v0.6.5
Security-focused release: token-at-rest hashing, OAuth PKCE, admin hardening, and session idle timeouts, plus OpenCode transcript parity and a broad refactoring pass.
DB Migrations
- 000053 — hash_session_device_tokens (#306):
web_sessions.idanddevice_codes.device_codeare now stored assha256(token)(hex) instead of the raw value, so a DB read (backup leak, SQLi, replica, support tooling) cannot replay live session/device tokens. The lookup-key format is not backward compatible across the migrate-then-deploy gap, so the migration deletes all rows inweb_sessionsanddevice_codesrather than hashing in place. Impact: every user is logged out and must re-login once. The demo shared session is re-provisioned by bootstrap on next start; any in-flight device-code flow expires within its 5-minute window. No column type change. - 000054 — web_session_last_activity (#314): adds nullable
web_sessions.last_activity_at TIMESTAMPas a sliding idle gate alongside the absoluteexpires_atcap, with a one-shot backfill (last_activity_at = created_at). The idle predicate usesCOALESCE(last_activity_at, created_at)for rollout-gap safety.
API Changes
- OAuth login now uses PKCE (S256) (#313): all three login endpoints (
GET /auth/{github,google,oidc}/login) generate acode_verifierstored in an HttpOnlyoauth_verifiercookie (MaxAge300) and sendcode_challenge+code_challenge_method=S256. Callbacks read and clear the single-use verifier cookie (rejecting with400if absent) and include it in the token exchange. No client action required. - Admin authorization is now a union (#308): Admin =
SUPER_ADMIN_EMAILS(env) OR the newusers.is_admincolumn. New endpointsPOST /api/v1/admin/users/{id}/grant-adminand.../revoke-admintoggle the column at runtime (no env edit + restart). Revoke is guarded against orphaning the last effective admin (returns409); granting aread_onlyuser is rejected (400). User-list rows now carryis_admin(raw column) andis_super_admin(in env);GET /api/v1/mereturnsis_adminas the union of both. - Typed-confirmation echo on destructive admin actions (#323):
POST .../users/{id}/deactivatenow requires body{ "confirm": "<target email>" };DELETE .../users/{id}?confirm=<target email>requires the email as a query param. Both are compared (trim + case-insensitive) server-side and rejected (400) on mismatch, after the last-admin guard. The card-invalidation endpoint now requiresconfirmto equal the freshly re-counted affected-session count on execute (dry_run: false), binding the action to the current blast radius. The last-effective-admin guard returns409and runs before any S3 wipe. - Pricing doc gains
cacheWrite1h(#309):GETpricing payload now exposescacheWrite1h(1-hour cache-creation rate, 2x input for Claude) alongsidecacheWrite(5-minute rate). Absent/0falls back tocacheWriteso older/remote docs never bill 1h tokens at $0. - Cost Distribution lowest band widened (#320): the histogram's two sub-$1 decades are merged into a single
$0.01 – $1band; from $1 up it remains one band per power of 10.
Security
- Hash web-session IDs and device codes at rest (#306) — see DB Migrations.
- OAuth 2.0 PKCE (S256) on GitHub / Google / OIDC (#313).
- Admin hardening —
SUPER_ADMIN_EMAILSstartup validation + last-admin guard (#315);is_admincolumn made load-bearing (union) + admin tag UI (#308); server-verified typed confirmation on destructive admin actions (#323). - Web session idle timeout (sliding window) + 7-day cap (#314).
- Cross-origin guard on
/auth/cli/authorize+/auth/device/verify(#307). - OAuth→existing-account email auto-linking now defaults OFF (#305).
- Device-verify brute-force lockout + remove user-code modulo bias (#304).
- Redact
git_infofor non-owner session access (#303). - Stop leaking owner email to anonymous viewers of public shares (#302).
- Remove dormant request/response body-logging middleware (#301).
- Build hardening — Dockerfile
-trimpath+ govulncheck CI gate (#298). TRUSTED_PROXY_HEADERSallowlist + rate-limiter bucket cap (#297).
Features
- OpenCode transcript Cmd-F search (shared search toolkit) (#318).
- OpenCode transcript cost side-rail (shared CostBar) (#317).
- OpenCode transcript minimap/timeline bar (shared TimelineBar) (#316).
- Split cache-creation billing by ephemeral tier (5m vs 1h) (#309).
- Self-host onboarding overhaul — canonical zero-config compose, Caddy profile, startup guard (#300).
Bug Fixes
- Default
Content-Typeon body-less mutating requests (#322).
Refactoring
useCardStatehook for the repeated card loading/error guard (#331).- Extract generic
ProviderFilterDropdown(#330). - Generalize transcript filter hooks across all three providers (#329).
- Extract shared
useApiDatahook fromuseTrends+useOrgAnalytics(#328). - Collapse
analytics/store.gocard get/upsert into generics + registry (#326). - Dedupe transcript error-reporting (Claude/Codex services) (#327).
- Dedupe
ToolsAnalyzertool counts bytool_use.id(#325). - Split
oauth.gointo per-protocol files (#324). - Deep package-by-package backend maintenance pass (#319).
- Data-drive
TrendsTokensCardcache-write row, drop the provider allowlist (#312). - Align per-session
TokensV2Cardlayout with the Trends tokens card (#311). - Add
opencodeAdapter.test.tsfor Claude/Codex test parity (#310). - Migrate per-session cost readers from flat
session_card_tokens(v1) totokens_v2(#295). - Contract test pinning backend↔frontend
getModelFamilyparity (#296).
CI
- Retry backend integration go test up to 3x in CI (#321).
Docs
- Reconcile
backend/.env.examplewithCONFIGURATION.mdenv-var reference (#299).
Breaking Changes
- All users are logged out on upgrade — migration 000053 deletes all
web_sessionsanddevice_codesrows (token-at-rest hashing). Every user re-logs in once; in-flight device-code flows must restart. - OAuth email auto-linking now defaults OFF (#305): a new OAuth login whose email matches an existing account is no longer auto-linked by default. Self-hosters relying on the old behavior must opt back in via configuration.
- New
TRUSTED_PROXY_HEADERSenv var (#297): client-IP / proxy headers are now read only from an explicit allowlist. Deployments behind a proxy that depend on forwarded headers must set this; otherwise those headers are ignored. SUPER_ADMIN_EMAILSis now validated at startup (#315): a malformed value fails fast at boot instead of being silently ignored.