Changelog
All notable changes to Chatalot are documented here. Versions follow Semantic Versioning.
v0.25.22 — 2026-05-30
Secure self-service bot provisioning (HIVE-105 Parts 1+2) + the angle-bracket render fix (CHAT-181). Ships on top of the 0.25.21 Integrations work (below), which prod had not yet received — so a prod upgrade from 0.25.20 to 0.25.22 includes everything in both entries.
Added
- HIVE-105 Part 2 —
POST /api/admin/bots/bootstrap-token: deploy-secret-gated mint of the initialbot:provisiontoken with NO human admin JWT. Gated byPROVISION_BOOTSTRAP_SECRET(X-Provision-Bootstrap-Secretheader, constant-time SHA-256 compare), mounted outside the JWT middleware, 404 when the secret is unset, finds-or-creates the integration bot (is_admin=false), mintsbot:provisiononly (never default/admin), idempotent.
Changed
- HIVE-105 Part 1 — the community-join surface (
add_bot_community,list_bot_communities,list_admin_communities) now accepts abot:provisiontoken or an admin JWT viarequire_provision_scope_or_admin. Abot:provisioncaller may add the bot as member role only.delete_bot,remove_bot_community,revoke_tokenstay admin-only. - CHAT-181 — the message markdown renderer escapes raw-HTML tokens to literal
text (
renderer.html), so<foo>,Map<String, Int>, comparisons,&, and quotes render intact; inline/fenced code keeps angle brackets without double-escaping; emoji + @mentions still render; user-typed raw HTML is no longer interpreted as markup. Renderer extracted tosrc/lib/markdown.ts.
Database
- No new migration. Migration
064_bot_token_scope_provision.sql(introduced in 0.25.21) applies on first start if the prod DB hasn't seen it — additivebot_tokens.scopeCHECK widening, existing rows untouched.
Tests
- 6 new server unit tests (HIVE-105: member-only role limit, constant-time secret compare, bootstrap idempotency); 15 web render tests (CHAT-181, vitest+jsdom); crypto byte round-trip. Server suite 123/123.
Dev-prove
- HIVE-105 Parts 1+2 + CHAT-181 dev-proven on the dev instance — see
.qa-proof/HIVE-105.mdand.qa-proof/CHAT-181.md.
v0.25.21 — 2026-05-29
Operator-friendly Integrations surface. Replaces "F12 → Application →
Local Storage → copy chatalot_access_token → paste into Hivemind
Connect" with a one-click wizard in the admin UI that mints a scoped
token whose blast radius is bounded by the scope itself.
Added
- CHAT-62b4cd3a — new admin Integrations tab. Wizard auto-creates
a per-integration bot user (named
integration-<sanitized-label>) AND mints a scoped token in one POST. Plaintext shown once. List view shows every active or expired non-default-scope token across all bots — the "what's connected to this chatalot" view operators have asked for. Inline revoke. - New bot-token scope
bot:provision— grantsPOST /admin/bots,GET /admin/bots,GET /admin/bots/{id}, andPOST /admin/bots/{id}/tokens. Cannot send messages, manage communities, suspend users, delete bots, or revoke other tokens. Intended for installer-style consumers like Hivemind Connect. POST /api/admin/integrationsandGET /api/admin/integrationsadmin routes (JWT-only — a scoped token cannot bootstrap more scoped tokens).
Changed
- The four bot-CRUD admin routes (
POST /admin/bots,GET /admin/bots,GET /admin/bots/{id},POST /admin/bots/{id}/tokens) now accept either an admin JWT (today's path) OR abot:provision-scoped bot token.DELETE /admin/bots/{id}andPOST /admin/bots/{id}/tokens/{tid}/revokestay JWT-only so a capturedbot:provisiontoken can't clean up its own tracks.
Database
- Migration
064_bot_token_scope_provision.sql— extends thebot_tokens.scopeCHECK constraint to allowbot:provision. Existing rows untouched.
Docs
- New ADR
adr-003-chatalot-scoped-bot-tokens-vocabularylocks scope naming convention, catalog, and the default-vs-scoped compatibility rule. - The Chatalot service docs gain an "Integration tokens" subsection.
docs/integrations/ai-workspace-setup.mdgets an Integration tokens path for installer-style consumers.
Related
- Phase 2 (CHAT-cf361bae) still adds the full scope catalog + middleware route-to-scope map. This work locks the vocabulary so Phase 2's map keys are stable.
v0.25.20 — 2026-05-29
UI-only follow-up to 0.25.19 — surface the bot-token scope dropdown on the admin Bots mint modal so the operator doesn't have to curl just to mint a metrics-scoped token.
Added
- CHAT-966a0305 — admin Bots tab mint modal: new "Scope"
<select>with two options (default— full bot permissions;metrics— read-only Vantage cockpit endpoint only) and a one-line consequence explainer that updates with the selection. - Tokens table on the bot detail panel gains a "Scope" column.
metricstokens render as a purple chip so an operator instantly knows they don't reach the messaging API.
Changed
- API client (
mintBotToken) accepts an optionalscope; omits the field when undefined to preserve the backend'sdefaultallow-list value. TokenSummaryandMintTokenResponseTS interfaces exposescope(backend already shipped both fields in 0.25.19).
Database
- None. No migration, no schema change. Behavior unchanged for existing
tokens — they all remain
scope=default.
Related
- Backstop for CHAT-cf361bae (Phase 2 still ships the full scope catalog + middleware route-to-scope map).
v0.25.19 — 2026-05-29
Vantage cockpit producer wiring + admin Bots UX cleanup. Both verified end-to-end on the dev instance before cutting.
Added
- CHAT-174 — Vantage cockpit metadata-only metrics endpoint
(
GET /api/v1/metrics/vantage). Returns instance version + health + workspace / member / channel / message counts. Zero message content, no PII, aggregate counts only — E2E encryption on chat messages is unaffected and intact even if a poller token is exposed. - Bot-token scopes (Phase 1, CHAT-174): new
bot_tokens.scopecolumn with allow-list (default,metrics).POST /api/admin/bots/{id}/tokensaccepts an optionalscope; default behavior unchanged for existing tokens. The metrics endpoint requiresscope=metrics; JWT humans and default-scope bot tokens (even admin) get 403. - CHAT-3f242d36 — Admin → Bots row gains a "Communities" column with a per-bot chip: yellow "No communities" for unattached bots (the typical post-Connect state), neutral count chip otherwise.
Changed
- CHAT-3f242d36 — Admin → Bots row button "Tokens" → "Manage". The panel it opens has held tokens + communities + bio + lifecycle for a while; the old label hid the door. No behavior change, label only.
Database
- Migration
063_bot_token_scope.sql—ALTER TABLE bot_tokens ADD COLUMN scope TEXT NOT NULL DEFAULT 'default'withCHECK (scope IN ('default','metrics')). Catalog-only write on Postgres ≥ 11; safe on a populated table. Existing tokens default to'default', behavior unchanged.
Related
- CHAT-cf361bae (filed) — Phase 2 follow-up: full scope catalog
(
webhook,relay, …) + middleware route-to-scope map so default-deny replaces per-handler opt-in. Not blocking.
v0.25.18 — 2026-05-25
Bot-as-client E2E foundations (ADR-002 Option B). Enabling work so an external bot crypto client (built in Hivemind) can participate in end-to-end-encrypted channels as a real member; the server never holds bot private keys.
Added
- CHAT-162 — the WebSocket
Authenticateframe now accepts a bot'sX-Bot-Token(cb_…) in addition to a JWT, routed through the same shared bot-auth as the HTTP API (active/unrevoked/unexpired token,is_bot, not suspended, rate-limit,suspended_users). Authentication only — authorization is unchanged (a non-member bot still cannot send to or read a channel). - CHAT-164 —
docs/developer-guide/bot-e2e-client.md: the authoritative chatalot-side contract a bot crypto client implements (key registration, X3DH, sender-key lifecycle, WS send/receive + rotation events, wire-format notes).
Changed
- CHAT-163 — removing an E2E-capable bot (one that has registered an identity key) from a community now rotates the affected channels' sender keys for forward secrecy, reusing the existing leave/kick rotation path. Keyless bots keep the cheap path.
Verified
- CHAT-165 — confirmed an
is_botuser can register keys, replenish prekeys, fetch its bundle, and post/get sender keys viaX-Bot-Token, with channel membership still enforced (non-member → 403).
v0.25.17 — 2026-05-25
Added
- Scheduled & recurring channel meetings (EPIC CHAT-155). A Meetings panel on each channel lets moderators+ schedule one-off or recurring meetings (RFC-5545 RRULE), with DST-correct recurrence and a shareable join link that deep-links back into the channel.
- CHAT-156 — backend:
scheduled_meetingsmodel + CRUD API + recurrence expansion (migration 061). - CHAT-157 — frontend: channel Meetings UI (schedule / list / edit / join / link); the meeting "room" is the existing voice channel.
- CHAT-158 — RSVP (going / maybe / declined), reminders before start via a background sweeper (WebSocket + offline web-push), and notify-on-schedule-change (migration 062).
- CHAT-159 — iCalendar (
.ics) export per meeting for Google / Outlook / Apple (recurring meetings included). - Add a bot to a community/channel (CHAT-160, part of CHAT-10). Instance admins can add a bot account to a community at a role from the Bots tab; the bot auto-joins the community's public channels and becomes a real member (manage via the API, post via webhooks). Reading/sending E2E message content as a bot is a separate effort (CHAT-161, ADR pending).
v0.25.16 — 2026-05-24
Fixed
- Community admins/owners couldn't delete webhook or others' messages — client
side (CHAT-148, follow-up). The server authz was fixed in 0.25.13, but the
client gated the "Delete (mod)" action on the channel role (
myRole) alone, so an admin, owner, or moderator who didn't create the channel (channel rolemember) never saw the option even though the server would allow the delete. A newcanModMessages()helper now also allows instance admin/owner and community owner/admin/moderator, mirroring the server. Verified in-browser: a community admin (not instance admin, not channel creator) sees and uses "Delete (mod)" successfully. - "What's New" popup showed an old version (0.23.0) after updating (CHAT-149 family). The in-app changelog array was frozen at 0.23.0, and the dialog fell back to the latest array entry when no entry matched the running version. Added a current entry and changed the trigger to only auto-show when there's a curated entry for the running version — it no longer presents an older version as "what's new."
v0.25.15 — 2026-05-24
Fixed
- Webhook messages rendered as "Deleted User" with no body on history load
(CHAT-147, follow-up). The 0.25.13 fix only patched the live WebSocket broadcast;
create_webhook_messagestores the webhook content in theplaintextcolumn with a\x00ciphertext placeholder, and the message read path (GET /channels/{id}/messages, threads) returned that placeholder and droppedplaintext. So on any reload or scrollback — i.e. essentially always — webhook messages showed "Deleted User" with an empty body. The read path now serves the webhookplaintextin theciphertextfield, consistent with the live broadcast, so the client renders the webhook name, avatar, and text. This also repairs already-stored webhook messages (they all haveplaintextpopulated). Caught by the release-lifecycle functional test (browser render check), which health/artifact checks missed. Known gap: pinned webhook messages use a separate model withoutplaintext(rare).
v0.25.14 — 2026-05-24
Fixed
- Web-client auto-update was silently disabled; new frontends never reached
already-loaded clients (CHAT-149). Both the client's baked
__APP_VERSION__and the server-advertised client version (static/version.json, sent in the WebSocketAuthenticatedmessage) are derived fromclients/web/package.json, which had not been bumped since 0.25.10. With both sides reading 0.25.10, the client's version-mismatch check (server_version !== __APP_VERSION__) never fired, so connected and cached clients never reloaded to pick up new frontend code — including the 0.25.13 webhook render fix (CHAT-147), which shipped in the image but stayed invisible to loaded clients (webhook messages still showed "Deleted User").package.json(and its lockfile) is now bumped in lockstep with the workspace version so the update signal moves each release; clients on an older build now detect the mismatch and silently reload into the new bundle. No server-code changes from 0.25.13. Follow-up: teach the release ceremony to bumppackage.jsonautomatically so this can't recur (CHAT-149).
v0.25.13 — 2026-05-24
Fixed
- Webhook messages displayed as "Deleted User" with a raw JSON body (CHAT-147). Real-time webhook broadcasts carried a placeholder ciphertext, so the client fell back to rendering the stored JSON payload and resolved the (null) sender to "Deleted User". Webhook messages now show the configured webhook name and avatar (or the per-message username override) and render the message text, matching how they already appeared in loaded history. The plaintext indicator was softened from "Webhook · not E2EE" to a quieter "via webhook" badge; the not-end-to-end-encrypted detail remains in its tooltip.
- Community owners and admins could not moderate webhook (or other) messages
(CHAT-148). Message deletion, pin/unpin, kick, ban, unban, and voice-kick
resolved the actor's role from
channel_members, so community owners and admins — whose role lives incommunity_members, not per channel — resolved to plain members and were denied. All channel-scoped moderation now resolves the community role through a sharedget_community_role_for_channelhelper, with instance owner/admin still promoted as before.
v0.25.12 — 2026-05-24
Fixed
- Webhook management returned 403 for community owners and admins (CHAT-145).
Webhook create/list/update/delete authorized against
channel_memberswith an owner-only check, so legitimate community owners and admins (and instance admins) were denied — blocking all webhook-based integrations (CI, alerts, announcements). Authorization now resolves the channel's community and allows owners or admins (and instance admin/owner), matching the documented policy ("Owner or Admin of the channel's community").
v0.25.11 — 2026-05-23
Fixed
- Self-hosting TURN docs corrected to the ephemeral scheme (CHAT-135).
docs/self-hosting/docker-deployment.mdshowed coturn using static--user=...:...long-term credentials, andconfiguration.md's.envtemplate still listedTURN_USER/TURN_PASSWORD/ICE_SERVERS— both contradicted the v0.24.0+ ephemeral HMAC scheme (TURN_AUTH_SECRET+TURN_URLS, coturn--use-auth-secret). Following the stale bits left TURN unconfigured, so/api/account/turn-credentialsreturned 503, voice silently fell back to STUN-only, and relay-dependent peers had no audio. Added a "no audio" voice section to the self-hosting troubleshooting guide.
Changed
- TURN misconfiguration is no longer silent (CHAT-135). The server logs a
startup WARN when it detects an identifiable TURN misconfig (deprecated
TURN_USER/TURN_PASSWORDset while the ephemeral pair is unset, orTURN_AUTH_SECRET/TURN_URLShalf-configured) and logs the specific reason (DEBUG) when/api/account/turn-credentialsreturns 503. The web client now emits a console warning when it falls back to STUN-only on a 503 instead of swallowing it. Reported by AIWF; the participant-panel discrepancy they saw was confirmed downstream of the dead media path, not a separate roster bug.
v0.25.10 — 2026-05-09
Added
-
Bot accounts and long-lived API tokens (CHAT-10). Operators can now create headless "bot" users from the Admin → Bots tab. Each bot is a regular
usersrow withis_bot = true; it has no password and no client identity keys. Bots authenticate to the API via theX-Bot-Token: cb_<64 hex>header, never JWT. -
6 admin endpoints under
/api/admin/bots: list, create, get, delete, mint token, revoke token. - Token plaintext is shown to the operator EXACTLY ONCE at mint time. The DB stores only the SHA-256 hash and an 11-char prefix for UI lookup.
- Tokens carry an optional expiry (hours from now, or never) and a free-form label.
unified_auth_middlewaretriesX-Bot-Tokenfirst, falls back to JWT Bearer. Mixed auth (both headers) is rejected to avoid identity ambiguity.- Per-bot-token rate limiting: default 60 requests / 10 second
window. Exceeding the budget returns HTTP 429 with
code: "rate_limited". Override per-token viabot_tokens.rate_limit_per_window. - Persistent (DB-backed) — survives container restart, mirroring the webhook rate-limit pattern.
- Admin UI: new Admin → Bots tab. Lists bot accounts, creates new bots, mints / revokes tokens, displays the one-time-show plaintext with a copy button.
Bots are otherwise normal users — they appear in member lists, can
be added to communities/channels via the usual flows, can be made
community/group admins, and respect existing permissions. Intended
consumer is docs/integrations/ai-workspace-setup.md.
Operator notes
- Migration
059_bot_accounts.sqladdsusers.is_botand creates thebot_tokenstable. - Migration
060_bot_token_rate_limit.sqladds thebot_token_rate_limitwindow-tracking table and therate_limit_per_windowoverride column onbot_tokens. - No public API surface change for human users. Existing JWT-Bearer flow is unaffected.
v0.25.9 — 2026-04-26
Security
- Community assets, group assets, and custom emojis are now
authenticated and membership-gated (CHAT-116). Previously the
/community-assets/{filename},/group-assets/{filename}, and/emojis/{id}routes were registered as public — anyone with a URL could pull the file. For private workspaces with confidential branding (customer logos, internal channel emojis, voice-call backgrounds), the URL leaked via referrer headers, browser history, shares, or screenshots.
The routes now sit behind auth_middleware AND each handler verifies
membership before serving:
- Community icon / banner: parses <community_uuid>_<role>.<ext>
from the filename and requires is_community_member.
- Custom emoji: looks up community_id on the emoji record and
requires is_community_member.
- Group icon / banner: parses <group_uuid>_<role>.<ext>,
resolves to the parent community, requires is_community_member.
- Voice background: parses <channel_uuid>_voicebg.<ext> and
requires channel_repo::is_member (channel-level membership).
- Instance admins / owners always pass.
- Non-members get 404, never 403, so the existence of an asset
is not disclosed.
Cache headers flipped from public, max-age=… to private, max-age=…
so shared caches (CDNs, proxies) don't serve one user's response to
another.
Added
- Service worker now intercepts gated asset fetches and attaches
the
Authorization: Bearer <token>header automatically. Required because<img>/<style url(...)>/<audio>can't set headers. The auth store posts the access token to the SW on login, refresh, and logout viapostMessage. Token lives in SW memory only — never persisted, never reachable from page-context JavaScript.
Operator notes
- Existing community/group/emoji asset URLs continue to work for members. Non-members previously got HTTP 200; they now get 404.
- A logged-out browser visiting a stale image URL (e.g. cached preview link) will get 401 / 404. The page calling the URL must be authenticated for assets to load.
v0.25.8 — 2026-04-26
Privacy
- Feedback now lands in a local table first (CHAT-124) —
POST /api/feedbackwrites a row to a newfeedback_submissionstable before any external fan-out. The operator's database is the source of truth; mirroring to GitHub Issues / Forgejo Issues is optional, best-effort, and fire-and-forget. Closes the cross-tenant leak risk where a managed-customer's feedback (potentially including UI screenshots) was posted to a vendor GitHub repo.
Added
- Forgejo Issues fan-out for feedback — when
FEEDBACK_FORGEJO_BASE_URL+FEEDBACK_FORGEJO_REPO+FEEDBACK_FORGEJO_TOKENare set, every feedback submission is mirrored as an issue on the configured Forgejo repo with aninstance:<host>label so operators triaging cross-instance can filter by source. Forgejo's API is GitHub-compatible (POST /api/v1/repos/{owner}/{repo}/issues). - Admin "Feedback" tab in the admin panel — lists submissions with status filter (new / triaged / closed / all), per-row expand with description + screenshot link, and Mark new / Mark triaged / Mark closed actions. Stat cards across the top.
GET /api/admin/feedback?status=— paginated queue list with per-status counts.PATCH /api/admin/feedback/{id}— update status. Audit trail viatriaged_at+triaged_by.GET /api/admin/feedback/{id}/screenshot— serves stored screenshot bytes with the original MIME type.
Deprecated
GITHUB_API_TOKEN+GITHUB_REPO_OWNER+GITHUB_REPO_NAMEenv forwarding remains for backward compatibility with self-hosters who already wired it, but is no longer the default. New Seglamater-managed instances should use the Forgejo path (or local-only).
Migration
058_feedback_submissions.sql— new table with screenshot blob, status enum, status+created_at index for queue queries, plusforwarded_to TEXT[]to record successful fan-out targets.
Operator notes
chat.seglamater.appanda staging instance: removeGITHUB_API_TOKEN/GITHUB_REPO_*env vars after applying v0.25.8 to fully sever the GitHub forwarding path. Local table- admin Feedback tab is the new triage surface.
v0.25.7 — 2026-04-26
Security
- WebSocket subscription drained on kick / leave / ban (CHAT-117) — the per-channel broadcast forwarder spawned by Subscribe checked membership at subscribe time but not on each forwarded event. A user whose membership was revoked mid-session continued receiving ciphertext + metadata until they disconnected. The forwarder now consults a per-(user, channel) revocation set on every event; kick, leave, and ban paths set the flag immediately and the forwarder drops the subscription on the next message. Set is cleared on legitimate re-subscribe so a re-invited user works normally.
- Community ban now rotates sender keys — sister bug to CHAT-115 (shipped v0.25.6 critical). The community ban path removed membership rows but never deleted the banned user's sender-key distributions or asked remaining members to rotate. The banned member retained chain-key seeds and could decrypt future ciphertext on those chains. Now mirrors the kick/leave flow.
- Voluntary channel leave now rotates sender keys — same class of
bug, smaller surface. Self-leaving a channel via
POST /api/channels/{id}/leavenow deletes the leaver's sender-key distribution and broadcastsSenderKeyRotationRequired.
Improved
- Communities default to
discoverable = FALSE(CHAT-118) — new workspaces are private-by-default. The invite-info endpoint redacts community name + description for non-discoverable workspaces, so a freshly-created internal/customer workspace no longer leaks its identity to invite holders. Existing rows are not touched; flip viaPATCH /api/communities/{id}{discoverable: true}if the workspace is meant to be public.
Added
- Invites can pre-assign a community role (CHAT-123) —
POST /api/communities/{cid}/invitesnow acceptsgrants_role: "moderator" | "admin". Authorization mirrorsset_member_role: only owners can mint admin-grant invites, only managers (admin or owner) can mint moderator-grant invites. Role is applied immediately afterjoin_communityon accept, with an audit log entry (community_invite_role_grant). Removes the dual-step "user joins → operator promotes" dance.
Migrations
056_discoverable_default_false.sql— flips column default for new rows; existing rows untouched.057_invite_grants_role.sql— addscommunity_invites.grants_rolewith check constraint.
v0.25.6 — 2026-04-26
Security
- CRITICAL: community kick / leave now rotates sender keys — the
channel-level kick path correctly deleted the leaving user's
sender-key distributions and broadcast
SenderKeyRotationRequiredto remaining members, but the community-level kick (and voluntary leave) skipped both steps. The leaving member retained chain-key seeds and could decrypt future ciphertext on those chains. Now both paths enumerate the community's channels and run the same rotation flow as a per-channel kick. Mirrorsroutes/channels.rs:402-410. Found by workspace-isolation audit pre-multi-tenant rollout.
Fixed
- community_repo::search_visible_users — ILIKE wildcard escape
applied. User-supplied search queries containing literal
%,_, or\no longer expand as wildcards. Same pattern asuser_repo::search_users.
v0.25.5 — 2026-04-26
Added
- Admin Updates: "Test connection" button (CHAT-106) — round-trips a signed request to the chatalot-updater sidecar and reports pass/fail with latency. Catches install-time misconfigs (env passthrough, secret mounts, network) before the operator hits Apply and the bug surfaces under the disruption window. Suggested by AIWF after CHAT-105's env-passthrough bug bit them.
- Admin Updates: in-flight apply state restoration (CHAT-113) — navigating away from the Updates tab and back no longer loses the apply progress panel. On tab mount, the UI now queries the updater for the most recent apply; if it's non-terminal, the progress panel re-attaches and polling resumes. Combined with humanized progress states from v0.25.3, the apply UX now survives all the typical ways an admin's browser session interacts with a long-running apply (idle nav, mid-apply refresh, secondary tab open).
- OIDC: auto-promote user matching ADMIN_USERNAME on first login
(CHAT-115) — for SSO-only deployments where the operator never had
a local account, the OIDC register path now grants
is_admin = trueto the first OIDC user whoseusernameclaim matches the configuredADMIN_USERNAMEenv var. Removes the dual-account dance the operator hit when bringing up SSO on a staging instance.
v0.25.4 — 2026-04-26
Improved
- Admin Updates: image digest visible in both Running and Latest cards
— when the running version matches the latest channel manifest, the
Running-now card now shows the digest of the running image (helpful
for cross-instance verification on the same version). The Latest-
available card shows the digest of the release that's about to be
installed. Full digest is in a collapsed
<details>for copying; the short prefix is shown by default to keep the card compact. - Admin Updates: prominent flag banners —
breaking_changesandsecurity_advisoryflags from the manifest now render as full-width banners with icons, hoisted ABOVE the version display so they can't be missed. Previously they were thin inline notes below the version, which the operator-as-customer scrolled past on the v0.25.0 apply test.
v0.25.3 — 2026-04-26
Improved
- Admin Updates: humanized progress states (CHAT-110) — the
apply progress panel previously displayed raw JSON like
State: {"state": "broadcasting_maintenance"}. The panel now shows a friendly label (e.g. "Notifying connected users"), a one-line description of what the orchestrator is doing, and a phase tag (Pre-flight / Active swap / Verifying / Rollback) so admins can see at a glance whether their instance is still in safe-to-abort territory or already in the disruption window. Each of the 12 in-progress orchestrator states has its own label + description.
v0.25.2 — 2026-04-26
Improved
- Admin Updates: stylized apply confirmation dialog — replaces the
browser-default
confirm()popup with the same Chatalot-themed modal used elsewhere in the admin panel. The confirmation now shows the current and target versions explicitly and a one-line note about the pre-disruption snapshot + automatic rollback. - Admin Updates: clearer post-apply success state — after a successful apply, the panel now surfaces the new version explicitly, explains that the browser is still running the previous build's JavaScript, and offers a one-click "Reload page" button to pick up the new build. Previously the only feedback was a brief one-line "Applied successfully" with no call-to-action.
v0.25.1 — 2026-04-26
Fixed
- Admin Updates UI: "Waiting for first state update..." hang (CHAT-108)
— chatalot-server's
/v1/apply/:iddeserializer required the fieldapply_id, but chatalot-updater'sApplyRecordJsonserializes it asid. Server returned 500 to its own UI on every poll, so the apply progress panel never advanced even when the apply was completing cleanly. Server now accepts bothidandapply_id, and bothcurrent_stateandstate, via serde rename + alias. Regression test (parses_sidecar_v1_apply_response_shape) locks the contract. - First Apply fails on snapshot — chatalot_backups volume owned by
root (CHAT-107) — the chatalot-updater image now runs as root long
enough for an entrypoint script to chown
/var/backups/chatalotto the updater uid (1001), then drops to updater viagosuand execs the binary. Required because compose-created volumes default toroot:root 755, which the binary couldn't write into. Existing customers with the chown workaround in place can either continue with that or pull the new updater image (manual recipe:docker compose pull chatalot-updater && docker compose up -d chatalot-updater). New bundle installs pick this up automatically.
Notes
- Wire-format of the
/v1/apply/:idresponse did not change. The drift was always in the server's deserializer; v0.25.1 makes the server tolerant. No client-side action needed beyond applying.
v0.25.0 — 2026-04-25
Security
- X3DH AD now bound into Double Ratchet AAD — the X3DH-derived
associated data (
IK_A || IK_B) is now included in the AAD passed to ChaCha20-Poly1305 for every Double Ratchet encrypt/decrypt, matching the Signal Double Ratchet spec §3.4 (ENCRYPT(mk, plaintext, CONCAT(AD, header))). This closes a subtle gap where a server-MITM substituting an identity key after the X3DH handshake would not have caused first-message decrypt to fail. AD was already computed correctly inx3dh.rsbut was discarded before reaching the ratchet AAD.
Breaking
- Wire-format AAD change invalidates existing DM sessions. Clients upgrading to 0.25.0 cannot decrypt messages from sessions established against pre-0.25.0 peers. All DM sessions automatically re-handshake via X3DH on next message — users will see a one-time "identity-key change" notice (TOFU) the first time they message a peer post-upgrade unless their peer also upgraded.
v0.24.6 — 2026-04-24
Fixed
- Admin "Updates" tab: 401 on every request — the new tab's fetch calls used raw
fetch()withcredentials: 'same-origin'and relied on cookies; chatalot-server authenticates viaAuthorization: Bearer <token>(pattern established for every other admin API call). Refactored to use the sharedapi.get/api.posthelpers from$lib/api/clientso the Bearer header is attached automatically. Caught by the operator on first real admin-UI visit after v0.24.5 deployed.
v0.24.5 — 2026-04-24
Added
- Admin "Updates" UI (CHAT-Phase-2c) — new tab in the admin panel showing the currently-running version, the latest available release on the configured channel, and an Apply button that HMAC-signs a request to the local chatalot-updater sidecar. Progress panel surfaces the apply state machine (succeeded / failed / rolled_back / recovered / frozen) via 3-second polling, tolerant of the brief 502 window while the server itself swaps. Breaking-change and security-advisory banners are rendered from the manifest fields. Release-notes link is pulled from the manifest's
release_notes_url. /api/admin/updates/{status,check,apply,apply/:id}endpoints — admin-only, with audit-log entries (update_applied) for every approved apply. Target-version is verified against the cached channel head before signing a sidecar request; typed-by-hand versions are rejected.services/updater_client— HMAC-SHA256 signer for/v1/applyand/v1/apply/:id, canonical format locked to the sidecar's wire format via a reference-vector unit test (so a drift on either side fails a build rather than silently 401ing at apply time).
Config
UPDATER_API_URL— URL of the local chatalot-updater sidecar (defaulthttp://chatalot-updater:8081via compose).UPDATER_API_TOKEN_FILE— mounted secret path; directUPDATER_API_TOKENenv also accepted. Falls back to/run/secrets/updater_tokenif neither is set.UPDATE_CHANNEL— canary|beta|stable (default stable).UPDATE_MANIFEST_HOST— base URL for channel-pointer fetches; defaulthttps://updates.seglamater.app. Override for self-hosted release channels.
Deferred (follow-ups)
- In-process manifest signature verification will land paired with the sidecar's wave-2 cosign work.
- Periodic background poll + "update available" nav badge — the tab currently loads on-demand.
v0.24.4 — 2026-04-24
Fixed
- Managed-update recreate preserves compose secret-file mounts + security hardening — the orchestrator's container recreate was copying
Bindsbut silently droppingHostConfig.Mounts(where docker compose deliverssecrets:entries, e.g./run/<omitted>/jwt_private_key),readonly_rootfs,security_opt,cap_add/cap_drop, andtmpfs. Net effect: the new container came up without its JWT / DB-password / TOTP-key files (ENOENT crashloop) AND without its hardening. Fix widens the allowlist inbuild_recreate_bodyto preserve all placement, mount, and security fields. Caught by the v0.24.3 smoke on a staging instance — the first end-to-end apply against a real registry image.
Smoke test milestone
First end-to-end managed-update apply on a staging instance succeeded after this fix:
signed /v1/apply → manifest fetch from updates.seglamater.app → cosign-pinned pull from
registry.seglamater.app → pre_flight → snapshot → migrate → swap → health pass. ~1 min apply time.
v0.24.3 — 2026-04-24
Fixed
- Updater sidecar
phase_pullnow uses the pinned@digestref (CHAT-58 follow-up) — with the bare repo path, bollard's create_image + the dockerd resolver occasionally landed on a cosign signature artifact tag (sha256-<prev-digest>) during tag-list lookups, which is a single-platform OCI artifact with no runnable linux/amd64 manifest. Switching to the full pinned ref bypasses tag-list resolution entirely: the daemon fetches the exact content-addressed manifest. - Ship
scripts/updater/pre_flight.sh+scripts/updater/migrate.shin the runtime image — the orchestrator execs these inside one-shot containers during an apply. They didn't exist in the repo previously, so the very first real apply died withexec: no such file or directory. Current stubs areexit 0; CHAT-59 tracks real implementations (binary sanity-check for pre_flight, schema migration for migrate).
v0.24.2 — 2026-04-24
Fixed
- Updater sidecar now passes registry credentials on
docker pull(CHAT-58) — the sidecar was making unauthenticated pulls and 401'ing against private registries (the production Seglamater one is aREQUIRE_SIGNIN_VIEW=trueForgejo). Added an optionalCHATALOT_UPDATER_REGISTRY_CREDS_FILEenv var (default/run/secrets/registry_creds). When the file is present with contents{"username":"…","password":"…","serveraddress":"…"}, credentials are forwarded to docker on every pull viaX-Registry-Auth. When absent or whitespace-only, pulls stay anonymous — suitable for public registries and for installations that don't use managed updates. Malformed JSON is a hard startup error (better than silently falling back to anon and 401'ing at apply time). Seesecrets/registry_creds.exampleand the self-hosting configuration docs. Per CHAT-13 Decision #1, clients receive read-only per-instance tokens during onboarding; write-scoped tokens stay vendor-side.
Operator migration note
Instances running the updater compose profile need to provide secrets/registry_creds (or touch it for anonymous pulls) before docker compose up -d. The install.sh helper (tracked in Phase 2d) will handle this automatically; manual deployments should either cp secrets/registry_creds.example secrets/registry_creds and edit, or touch secrets/registry_creds to opt out of authenticated pulls.
v0.24.1 — 2026-04-24
Fixed
- Updater sidecar pre_flight/migrate/start_new phases now use pinned image refs (CHAT-57) — the orchestrator was using
manifest.imagebare (no tag) at three phases that spin up or recreate containers. Docker resolves a bare ref as:latest, which doesn't exist in the canonical registry, so the sidecar failed withNo such image: ...chatalot:latestas soon as a real (non-placeholder) manifest was applied. AddedReleaseManifest::pinned_ref()returning<image>@<image_digest>and switched the three affected phases to use it. Belt-and-suspenders: error-out if either field is missing at these phases rather than falling back to bare/empty. Discovered during the v0.24.0 canary smoke test on 2026-04-24. - Updater phase order: pull now precedes pre_flight (CHAT-57 ctd.) — bollard's container-create does not auto-pull, so pre_flight and migrate cannot run against an image that hasn't been pulled yet. Previously pull happened AFTER pre_flight; the bare-image bug masked this ordering issue. Moved
phase_pullto position 3 (after verify_image + previous-image capture, before pre_flight). Still pre-disruption; a pull failure still triggersfail_cleanwith no rollback needed.
Release-pipeline internals (operator-only, not user-facing)
chatalot-releaseCLI: sign-blob via cosign 3.x--bundleformat (deprecated--output-signature -broke cosign 3.0.6).chatalot-releaseCLI: rsync--omit-dir-timesso the deploy phase exits cleanly against the rrsync-wo target on the newupdates.seglamater.apphost.
v0.24.0 — 2026-04-24
Security
- Webhook plaintext disclosure (CHAT-9) — webhook messages are stored in plaintext on the server (by design -- external integrations can't participate in per-user E2E encryption). The UI now distinguishes them with a yellow "Webhook · not E2EE" badge in the message header, the webhook creation form shows an explicit disclosure warning, and
docs/user-guide/webhooks/using-webhooks.mdopens with a Security & Privacy section.docs/self-hosting/security-hardening.mdadds a "Message Encryption Coverage" table so operators understand which message types are E2E and which aren't. No behavior change for existing deployments -- webhook storage is unchanged; this is a disclosure pass. - Server-side message search removed (CHAT-8 Phase 1) — the legacy
GET /api/channels/:id/messages/searchandGET /api/messages/searchroutes, along with the underlyingsearch_messagesandsearch_messages_globalrepo functions, have been removed. They worked by runningconvert_from(ciphertext, 'UTF8') ILIKE $patternagainst the messages table, which only functioned because messages were still stored as UTF-8 plaintext in theciphertextcolumn (a Phase 1 carry-over predating the full E2E rollout). That contradicted the end-to-end encryption claim. The client search UI is now disabled with a banner explaining the rebuild. Phase 2 will reintroduce search as a client-side Tantivy-WASM index that never touches the server (tracked separately). - Ephemeral TURN credentials (CHAT-15) — coturn is now configured with
--use-auth-secret, a shared HMAC-SHA1 key between chatalot-server and coturn. The server issues short-lived credentials (default 1h TTL) via the newGET /api/account/turn-credentialsendpoint, authenticated via JWT. Previously, one staticTURN_USER/TURN_PASSWORDwas shared by every user forever. New env vars:TURN_AUTH_SECRET(required when theturncompose profile is active),TURN_URLS(comma-separated list returned to clients),TURN_CREDENTIAL_TTL_SECS(60-86400, default 3600). The legacyice_serversfield onGET /api/auth/configremains for one release for back-compat. - TLS + DTLS on coturn (CHAT-14) — removed
--no-tls --no-dtls, added a self-signed cert generated at first boot (stored in theturn_certsnamed volume; regenerate by deleting the volume). TURN now listens on TCP/5349 (TLS) and UDP/5349 (DTLS) in addition to plaintext 3478. WebRTC clients will prefer the encrypted transport. - Persistent login lockout (CHAT-16) — failed-login state is now stored in the
login_attemptstable (migration 053) instead of an in-memoryDashMap. Lockouts now survive server restarts. Previously, restarting the container would reset the counter and let brute-force attempts resume immediately. - Fail-closed on missing
TOTP_ENCRYPTION_KEY(CHAT-18) — the server now refuses to start whenTOTP_ENCRYPTION_KEYis unset or empty. Previously it would log a warning and continue, silently encrypting TOTP secrets with a key derived from the empty string. Operators upgrading must setTOTP_ENCRYPTION_KEYin.env(or mount/run/secrets/totp_encryption_key). Generate withopenssl rand -hex 32. - Pinned
wasm-packversion (CHAT-17) — the Docker build now installswasm-pack 0.13.1viacargo install --lockedinstead of piping the upstream install script throughsh. Reproducible, supply-chain-auditable.
Added
chatalot-updatercrate — DB snapshot and restore primitives for the managed-update system (CHAT-17). Newchatalot-snapshotCLI binary with subcommandscreate,list,restore,verify,prune. Snapshots are gzippedpg_dumpoutput stored at/var/backups/chatalot/{timestamp}-{version}.sql.gzwith a JSON sidecar manifest. Configurable viaCHATALOT_SNAPSHOT_DIR(default/var/backups/chatalot),CHATALOT_SNAPSHOT_RETAIN_COUNT(default 5),CHATALOT_ROLLBACK_WINDOW_HOURS(default 24, max 168). Passwords pass viaPGPASSWORDenv var only — never on the command line. Seedocs/self-hosting/backup-and-restore.mdfor usage.- Admin audit log richness (CHAT-12) — every admin endpoint now records IP address (via trusted-proxy-aware extraction), user-agent (truncated to 512 bytes), and before/after state for each mutation. Action strings are now drawn from typed constants in
chatalot-db::repos::audit_reposo there's one source of truth. Reserved action names for the upcoming managed-update system (update_applied,update_rolled_back,release_channel_changed, etc.) are defined alongside; they're wired up by the updater subsystem.
Changed
- Configurable coturn listener ports (CHAT-44) — the coturn sidecar's listening ports are now driven by
TURN_LISTENING_PORT(default3478) andTURN_TLS_PORT(default5349) instead of being hardcoded into the compose command. Default behavior is unchanged — admins who don't touch their.envget the IANA standard ports. Override when another service on the host already owns one of the defaults (most commonly a Tailscale/Headscale DERP relay using UDP/3478). If you override, remember to updateTURN_URLSand your firewall rules to match. Port-conflict guidance added todocs/self-hosting/configuration.md. - Release-manifest host migrated to
updates.seglamater.app(CHAT-41) — the canonical location for Chatalot's signed release manifests and the cosign public key is published atupdates.seglamater.app. Self-hosting clients running v0.24.0+ poll the new URL; the public key at the new location is the same material (SHA-256f1ff3f7ff3a1a7fe53620bbd8db0e362e55dcf622780e271740bc27d1850d082). No operator action needed for existing deployments — the updater sidecar reads the URL from its configured build, so upgrading to 0.24.0 automatically moves clients to the new host. The signed manifests are published from theseglamater/chatalot-updatesrepo.
Added
chatalot-releaseCLI (CHAT-Phase-2a) — operator-only binary atcrates/chatalot-releasethat automates the six-phase release ceremony (preflight → git tag → docker build + push → cosign sign + verify → manifest generate + sign-blob → channel promote + rsync deploy + post-deploy verify). Not included in the runtime image; invoked on the operator workstation during a human-supervised release. Dry-run mode prints the full plan without touching state. Seedocs/developer-guide/releasing.mdfor usage.
v0.23.0
Added
- OIDC/SSO support — login via Authentik, Keycloak, or any OpenID Connect provider
- OIDC user provisioning (auto-create accounts on first SSO login)
- Option to disable password login when OIDC is enabled (
OIDC_DISABLE_PASSWORD_LOGIN) - CI workflow for automated testing on push/PR (cargo check, clippy, frontend build)
- Root-level
SECURITY.md,CONTRIBUTING.md, andCHANGELOG.md - TURN/coturn documentation in self-hosting guide
- Build hash cache-busting for static assets
Fixed
- CSP no longer hardcodes specific domains — WebSocket origins derived from
PUBLIC_URL - CORS fallback no longer leaks author's domain — derived from
PUBLIC_URL - Fixed
og:imageusing absolute URL (now relative) - Version sync across all manifests (workspace, desktop, frontend)
Security
- Removed internal homelab hostnames from CSP headers
- Added responsible disclosure policy (
SECURITY.md)
v0.22.6
Added
- UI zoom (50–200%) with keyboard shortcuts (Ctrl+/Ctrl-), synced across tabs
Fixed
- Sidebar tab not remembering last selection
- Removed dead Tauri bridge iframe code
v0.22.5
Fixed
- All TypeScript errors resolved across the frontend codebase
- Removed dead shell/console feature code
v0.21.0
Added
- Gallery channels with image thumbnails and lightbox viewer
- Automated backup script with remote pg_dump and file sync
- GitHub Action to auto-update AUR package on release
.desktopfile categories for Linux packaging
Fixed
- 5 WebRTC video/screen sharing bugs
- WebRTC memory leak in voice connections
- Voice rate limiting and broadcast channel hardening
- ICE candidates silently dropped (voice calls failing)
- Voice auto-kick race condition with multiple tabs
- RwLock crash risk in concurrent access paths
- Group invite permissions for instance owner
- 3-person mesh call connectivity
- Desktop app auto-update (navigate to server SPA on version mismatch)
- Docker build race condition (separate cargo cache IDs per stage)
- CSP blocking SvelteKit (per-request nonce injection)
Security
- Multiple security hardening rounds: CORS, TOTP encryption, EXIF stripping, WebSocket auth
- JWT audience validation, prekey verification
- Secrets management overhaul, rate limiting, auth audit logging
- Fixed 2 critical + 5 high findings from security audit
- CSP hardened with nonce-based script policy
- Replaced
unwrap()with proper error handling in channel routes
v0.20.0
- Permissions overhaul: unified 5-tier role hierarchy (Instance Owner > Instance Admin > Owner/Admin > Moderator > Member)
- Instance Owner can now delete any message anywhere, including other users' DMs
- Instance Admin now bypasses community membership checks (previously only Instance Owner could)
- Channel moderator role: owners can promote members to moderator in channel member panels
- Moderators can delete others' messages, pin/unpin messages, close polls, and kick/ban members
- Role cycling in channel member panel for intuitive role management
- Consolidated duplicate permission logic across server codebase
v0.17.0
- Add ARM64 support with multi-architecture Docker builds (amd64 + arm64)
- Add pre-built container images on GHCR
- Add CI workflow for automated multi-arch builds
- Add platform detection in install script
v0.16.0
- Add threaded replies with thread panel UI
- Add read receipts with real-time broadcast
- Add privacy toggle for read receipts
- Thread panel: rich message rendering, hover actions, reactions, composer
v0.15.0
- Add message edit history tracking
- Add edit history viewer UI
- Track old ciphertext/nonce on each edit
v0.14.x
- Add search filters: sender, date range, file type
- Add scheduled messages with send-later UI and scheduled panel
- Add bookmarks ("Saved Items") panel
- Add bio and pronouns to user profiles and profile cards
- Accessibility improvements: ARIA labels throughout
- v0.14.1: Error feedback improvements, minor fixes
v0.13.x
- Add announcements system (admin → all users)
- Add custom emoji (50 per community, PNG/GIF/WebP)
- Add content reporting system
- Add idle status tracking
- v0.13.1: Mark-all-read, confirmation dialogs, type fixes
- v0.13.2: Accessibility and code quality improvements
- v0.13.3: Responsive UI and server hardening
v0.12.0
- Add polls (2-10 options, multi-select, anonymous, expiry)
- Add webhooks (per-channel, configurable name/avatar)
v0.11.0
- Add theme customization: 8 color palettes, 8 accent colors
- Add message density options (cozy/compact)
- Add font size settings (small/medium/large)
- Add custom theme support
v0.10.0
- Add permissions system for groups, channels, and communities
- Add group and channel settings popovers
- Add community policies (who can create groups, who can create invites)
v0.9.0
- Add mobile-responsive layout
- Add touch-friendly UI components
- Add adaptive media sizing for small screens
v0.8.0
- Add preset themes: Default, Monokai, Dracula, Nord, Solarized, AMOLED, Catppuccin
- Add custom theme editor
- Add bubble-style message layout option
- Add relative timestamps
- Add reduce-motion accessibility option
v0.7.0
- Add invite link system with codes, expiry, and usage limits
- Add WebSocket connection cleanup and reconnection
- Add accessibility improvements
- Security hardening: rate limiting, input validation
v0.5.x
- Add voice channel volume amplification (0-500%)
- Add screen sharing with audio pipeline
- Fix screen share context menu and audio capture
- Add PipeWire system audio auto-capture
v0.1.0
Initial release.
- Communities, groups, and channels
- Text messaging with Markdown formatting
- Voice and video calls (WebRTC full-mesh)
- Direct messages
- End-to-end encryption infrastructure (X3DH + Double Ratchet + Sender Keys)
- File uploads with previews
- Emoji reactions
- User profiles with avatars, status, and presence
- Admin panel (users, invites, files, reports, audit log)
- Moderation tools (warn, timeout, kick, ban)
- Dark/light theme with system preference detection
- Docker deployment with PostgreSQL
- PWA support
Versioning Policy
Chatalot uses semantic versioning: - Major (x.0.0): Breaking changes to the API or database schema - Minor (0.x.0): New features, non-breaking changes - Patch (0.0.x): Bug fixes, minor improvements
Database migrations are applied automatically on server startup. Breaking schema changes will include migration scripts.
Related Pages
- Feature Status -- Current implementation status of all features
- FAQ -- Frequently asked questions