Skip to content

fix(console,api): close every P0/P1 and the first 20 P2s from the console audit - #22

Merged
lokesh-danu merged 1 commit into
mainfrom
dev
Aug 7, 2026
Merged

fix(console,api): close every P0/P1 and the first 20 P2s from the console audit#22
lokesh-danu merged 1 commit into
mainfrom
dev

Conversation

@lokesh-danu

Copy link
Copy Markdown
Collaborator

Closes §3.1–§3.5, §4.1–§4.5 and §5.1–§5.20 of the console functional audit, and adds the audit itself (docs/CONSOLE_AUDIT.md) so the remaining findings stay readable and numbered.

Every fixed finding keeps its original diagnosis intact with a FIXED note beneath it, so a wrong call in the analysis stays visible rather than being edited away.

Why this is one PR

The audit's verdict was that the console is not scaffolding — all 23 views are real components bound to real endpoints — and that what blocked going live was three separate things: no release automation at all, a cluster of contract mismatches that render confidently wrong answers, and three security defects in the serving and credential path. Those turned out to be heavily interlocked (the error-envelope split masked the 401 classification, which masked the auth gate, which masked the session boundary), so they were fixed in dependency order rather than by severity.

What changed

Release path (§3.1–§3.2) — there was no CI, and the Docker image never built the console. Both failures were silent by design. Adds .github/workflows/{ci,release}.yml, a node:20-slim builder stage, and an allowlist .dockerignore so a stale local dist/ cannot reach the image.

Security (§3.3–§3.5)?api= sent the workspace API key to any origin; it is now compiled out of production builds rather than validated at runtime. Saving the guard policy destroyed unmodelled keys, including every allow rule under a default: deny policy — a total egress lockout with no error shown. Two ErrorBoundaries replace a blank page.

Contract (§4.1–§4.5) — one error envelope for the whole product, so RyaError's vocabulary stops rendering as the literal string HTTP 400. 401s are classified rather than assumed stale. The user-identity flow is wired end to end (and an MT server gap closed with it), and the approvals gate now shows what is being approved.

Console correctness (§5.1–§5.17) — server-side paging for runs and conversations; the view subtree keyed on the agent, so Save can no longer write agent A's draft into agent B's policy; a poll that schedules itself instead of a setInterval; stale data that carries its age; one broadcast refresh signal; unknown counts distinguished from real zeros in four places. A session is now a component lifetime, so signing out discards the previous tenant's data instead of leaving it one keystroke behind the dialog. The auth gate asks the runtime instead of inferring from localStorage, so a default rya serve is usable — and it removes a request rather than adding one. Destructive tool actions are confirmed and carry the operator's own audit reason instead of a hardcoded string.

Serving path (§5.18–§5.20) — CORS omitted DELETE, so revoking an API key and removing a member both died at the preflight cross-origin. index.html carried no Cache-Control while content-hashed assets were revalidated on every load — backwards in both directions, and the index half is a blank page after every deploy. CSP connect-src carried bare ws: wss:, permitting a socket to any host for a console that opens none.

All three of those were invisible in the only deployment anyone develops against — same-origin rya serve, no CDN, a channel never opened. That is why the fix that matters most there is scripts/smoke_console.sh, which asks a live server what came back: 15 checks over security headers, cache policy, and every asset the index actually references.

Verification

Console tests 164 → 377 across 30 files
Python tests 899 → 931 passing locally, 62 skipped (992 with Postgres available)
TypeScript SDK 56 passing
Types / build tsc --noEmit clean, vite build 0 warnings, 0 act(...) warnings
Bundle 336 kB → 356.8 kB (99 → 106.2 kB gzip)
Smoke 15/15 against a live server on the rebuilt bundle

Every new assertion was verified as a negative control against the unfixed code, file by file, and the assertions that legitimately pass either way say so in a comment at the assertion explaining what they do guard.

Still open

§4.6–§4.10, §5.21–§5.27, and the stub inventory in §6 — all documented in docs/CONSOLE_AUDIT.md with the recommended order in §11a. The highest-value remaining item is §4.7: remove_member returns {"removed": false} and revoke_key returns {"ok": false}, and the console reads neither, so an operator believes a compromised key is dead when it is not.

One new finding was recorded but deliberately not fixed: a cross-origin preflight to /mcp on a token-protected runtime is answered 401 with no CORS headers, because the MCP token guard short-circuits ahead of CORS. It is exactly the behaviour that moving the CORS registration would have changed, which is why that refactor was kept out of a missing-verb fix.

🤖 Generated with Claude Code

…sole audit

`docs/CONSOLE_AUDIT.md` audited the React operator console, its contract with
`src/rya/api/app.py`, and the build/serve/release path. This closes §3.1-§3.5,
§4.1-§4.5 and §5.1-§5.20, and adds the audit itself so the remaining findings
(§4.6-§4.10, §5.21-§5.27, §6) stay readable and numbered.

Every fixed finding keeps its original diagnosis intact with a FIXED note beneath
it, so a wrong call in the analysis stays visible rather than being edited away.

Release path (§3.1-§3.2). There was no CI and the Docker image never built the
console -- both failures silent by design. Adds `.github/workflows/{ci,release}.yml`,
a `node:20-slim` builder stage, and an allowlist `.dockerignore` so a developer's
stale `dist/` cannot be copied into the image.

Security (§3.3-§3.5). `?api=` sent the workspace API key to any origin and is now
compiled out of production builds. Saving the guard policy destroyed unmodelled
keys, including every allow rule under a `default: deny` policy. Two ErrorBoundaries
replace a blank page.

Contract (§4.1-§4.5). One error envelope for the whole product, so `RyaError`'s
vocabulary stops rendering as the literal string `HTTP 400`. 401s are classified
rather than assumed stale. The user-identity flow is wired end to end, and the
approvals gate shows what is being approved.

Console correctness (§5.1-§5.17). Server-side paging for runs and conversations;
the view subtree keyed on the agent; a self-limiting poll that schedules itself
instead of a `setInterval`; stale data that carries its age; one broadcast refresh
signal; unknown counts distinguished from real zeros in four places. A session is
now a component lifetime, so signing out discards the previous tenant's data
instead of leaving it behind the dialog. The auth gate asks the runtime rather
than inferring from localStorage, so a default `rya serve` is usable. Destructive
tool actions are confirmed and carry the operator's own audit reason.

Serving path (§5.18-§5.20). CORS omitted DELETE, so revoking an API key and
removing a member died at the preflight cross-origin. `index.html` had no
`Cache-Control` while content-hashed assets were revalidated on every load --
backwards in both directions, and the index half is a blank page after a deploy.
CSP `connect-src` carried bare `ws: wss:`, permitting a socket to any host for a
console that opens none. All three were invisible in the one deployment anyone
develops against, which is why `scripts/smoke_console.sh` now asks a live server
what came back: 15 checks over headers, cache policy and every asset the index
references.

Tests: console 164 -> 377 (30 files), Python 899 -> 931 passing locally (992 with
Postgres available), TypeScript SDK 56. `tsc --noEmit` clean, `vite build` 0
warnings, 0 `act(...)` warnings, smoke 15/15 against a live server.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lokesh-danu
lokesh-danu merged commit 98e8739 into main Aug 7, 2026
7 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant