Skip to content

v0.7.4

Latest

Choose a tag to compare

@mkjt2 mkjt2 released this 06 Oct 21:03
· 7 commits to main since this release
a73c7a9

Security

  • Recheck account eligibility on CLI and device credential routes (#428): /auth/cli/authorize, /auth/device and /auth/device/verify now treat an inactive account as logged out, so it can no longer mint an API key or authorize a device. CLI authorize also enforces ALLOWED_EMAIL_DOMAINS. POST /auth/device/token always reloads the authorizing user and returns 403 access_denied (and deletes the code) if that user was deactivated, made read-only, deleted, or falls outside the domain allow-list.
  • Unify post-login redirect validation (#427): one open-redirect gate for the OAuth callbacks, password login and /auth/logout?redirect=. Rejects absolute, protocol-relative, backslash and control-character values. Password login now honors the redirect form field (fixes the device flow and deep links on password-auth deployments landing on /) and ignores stale OAuth post_login_redirect cookies.
  • Consume device codes atomically with API key issuance (#424): concurrent /auth/device/token polls could both mint a key (the second replacing the first). The claim and key issuance now happen in one transaction; exactly one poll wins, the rest get invalid_grant. A key-limit 409 leaves the code retryable.
  • Enforce an 8–72 byte password policy (#423): admin create-user returned a 500 for passwords over 72 bytes, and password login accepted anything sharing a valid password's first 72 bytes. Passwords are now validated as 8–72 bytes (never truncated) for admin create-user, ADMIN_BOOTSTRAP_PASSWORD, and password login.
  • Valid dummy bcrypt hash for nonexistent password users (#422): the timing-equalization dummy hash was malformed, so unknown emails answered in ~1 ms vs ~250 ms for known ones, enabling account enumeration on POST /auth/password/login.
  • Device flow responses no longer cacheable (in #418): /auth/device/code and /auth/device/token now send Cache-Control: no-store on success and on every error.

Bug Fixes

  • Reject Codex self-parent links across equivalent UUID spellings (#425): parent_thread_uuid equal to thread_uuid in a different case or spelling (unhyphenated, braced, urn:uuid:) is now rejected with 400.
  • Make nested Claude subagents findable (#421): depth ≥ 2 subagents are now reachable via a per-level "Launched here (n)" dropdown and a path row; "All subagents" counts every agent file. Also fixes useTranscriptData briefly returning the previous file's lines when switching sources.

Performance

  • Stop the analytics recompute loop and cut compute memory ~7x (#416): computes longer than the 5s DB timeout never cached, so every poll recomputed the whole session and could exhaust a 512MB VM. Cache-miss computes are now detached from the request, deduped per session, and cached even if the client leaves. Stale cards are served immediately, with a background refresh when they are more than 2 minutes old. Analytics parsing no longer memoizes agent files or keeps a raw map per line.
  • Stream sync/file and files/download (#417): transcripts are streamed from chunk data with periodic flushes instead of building merged copies. Peak memory for a 15MB transcript drops from ~110MB to ~32MB. line_offset now filters by absolute line number (fixing wrong lines when chunks had gaps), and responses are newline-terminated.
  • Raise analytics agent-file cap from 200 to 500 (#420).

Pricing

  • Add OpenAI GPT-6.1 Sol (gpt-6.1-sol) (#415): $2 / $10 input/output per MTok, cache read $0.10 (0.05x input). Long-context tier not included.
  • Add Claude Sonnet 5.5 (sonnet-5-5) (#414): $2 / $10 input/output, cache write $2.50 (5m) / $4 (1h), cache read $0.20.

Configuration

  • Smart recap: switch the production OpenAI recap model to gpt-6-luna (#413): fly.toml and all example configs/docs now reference gpt-6-luna instead of gpt-5.6-luna. Self-hosters keep whatever SMART_RECAP_MODEL they set. Existing recaps are not regenerated.

Maintenance

  • Backend maintenance (#418): Go 1.26.6 → 1.27.1 (go.mod, CI, both backend Dockerfiles); bumps andybalholm/brotli 1.2.6, klauspost/compress 1.20.1, moby/moby/api 1.56.0; Go 1.27 modernizers; minor cleanup.
  • Frontend dependency bumps and audit fix; dedupe transcript-pane scroll helpers (#419): in-range updates (storybook 10.6.1, react-query 5.104.0, dompurify 3.4.16, react-router-dom 7.18.4, zod 4.6.5, …), npm audit clean. The four transcript panes now share virtualized-scroll helpers and a useFirstVisibleIndex hook.
  • Document and test the device-verify limiter's soft capacity bound (#426): docs and tests only; live lockouts are never evicted.

Breaking Changes

  • No renamed or removed env vars.
  • Passwords must be 8–72 bytes. Admin create-user returns 400 "Password must be at most 72 bytes" for longer passwords, and an over-long ADMIN_BOOTSTRAP_PASSWORD fails startup with a clear message (it already failed before, with a raw bcrypt error). Password login rejects passwords over 72 bytes; no existing user is affected, since no stored hash can belong to a longer password.
  • Inactive or ineligible accounts can no longer obtain CLI credentials. /auth/cli/authorize redirects inactive users to /login and users outside ALLOWED_EMAIL_DOMAINS to /login?error=access_denied. POST /auth/device/token can now return 403 access_denied even with no domain allow-list configured.
  • CLI impact: no CLI release required. The new device token 403 uses the standard RFC 8628 access_denied error code.
  • GET /api/v1/sessions/{id}/sync/file with line_offset: the response is now newline-terminated and filtered by absolute line number.
  • Building from source requires Go 1.27+.

DB Migrations

None.

API Changes

No endpoints were added or removed. Behavior changes to existing endpoints:

  • POST /auth/device/token: documented full response table. New 403 {"error": "access_denied"} whenever the authorizing user is no longer eligible (deactivated, read-only, deleted, or outside ALLOWED_EMAIL_DOMAINS), not only when a domain list is configured. Concurrent polls: exactly one 200, the rest 400 invalid_grant. 409 api_key_limit_exceeded leaves the code retryable.
  • POST /auth/device/code, POST /auth/device/token: responses (including errors) carry Cache-Control: no-store.
  • GET /auth/cli/authorize: inactive accounts are redirected to /login; read-only identities and users outside ALLOWED_EMAIL_DOMAINS are redirected to /login?error=access_denied and no key is minted.
  • POST /auth/password/login: honors the optional redirect form field (relative paths only; /auth… stays a backend path, anything else gets FRONTEND_URL prepended). Precedence: pending CLI flow, then redirect, then FRONTEND_URL. Ignores and clears the post_login_redirect cookie. Passwords over 72 bytes redirect to /login?error=Password is too long without counting as a failed attempt.
  • GET /auth/{github,google,oidc}/login redirect param and GET /auth/logout?redirect=: absolute, protocol-relative (//), backslash, or control-character values are ignored.
  • POST /api/v1/admin/users: password must be 8–72 bytes; over-long passwords return 400 instead of 500.
  • POST /api/v1/sync/chunk (codex_rollout): parent_thread_uuid is compared to thread_uuid as parsed UUIDs, so any spelling of the same UUID returns 400.
  • GET /api/v1/sessions/{id}/sync/file: line_offset returns lines whose absolute line number is greater than it; every returned line ends in \n. Response is now streamed.
  • GET /api/v1/sessions/{id}/analytics: may serve cached cards with a stale (lower) computed_lines while a background refresh runs (refresh only when cards are more than 2 minutes old). First view or a card schema version bump still waits for the compute. validation_error_count is set only on responses that waited for a fresh compute.