Skip to content

feat(coord): browser approval console (token-gated, read+decide)#77

Merged
escapeboy merged 2 commits into
masterfrom
feat/mgmt-console
Jul 17, 2026
Merged

feat(coord): browser approval console (token-gated, read+decide)#77
escapeboy merged 2 commits into
masterfrom
feat/mgmt-console

Conversation

@escapeboy

Copy link
Copy Markdown
Owner

Adds GET /console — an operator approval console served by the coordinator: a static, data-free HTML shell that lists runs paused at an approval gate and lets an operator approve/reject them from a browser. Read + decide only; no trigger/cancel (higher blast radius, deferred).

Security model (built against a dedicated review; adversarial re-review returned GO)

  • Shell is exempt from auth (like /api/health) because a browser navigation carries no bearer header. It embeds zero run data and zero secrets.
  • All data reads (/api/runs, /api/runs/{id}) and the mutation (/api/runs/{id}/approve) go through the existing authed endpoints via fetch() with an operator-supplied bearer token held only in memory and sent only in the Authorization header — never in a URL or storage; cleared on unload.
  • All dynamic DOM is built with textContent/createElement (never innerHTML) → a hostile run_id/step_id cannot inject markup or script.
  • Approvals still require the per-gate S9 token, typed by the human.
  • Response headers: X-Frame-Options: DENY + CSP default-src 'none'; …; frame-ancestors 'none'; base-uri 'none' + Cache-Control: no-store.

Verification

  • Full --features serve test suite green (coordinator 53 incl. 2 new security tests: shell served without bearer / carries no data / has headers; /api/runs still 401s without bearer). fmt + build clean.
  • Live end-to-end proven: coordinator over a real paused approval-gate run — shell public + no data, /api/runs 401 without token / lists the paused gate with token.

2 files touched region (crates/llmvm-cli/src/coordinator.rs), +~250 lines. No existing behavior changed.

Adds GET /console — an operator approval console served by the coordinator: a
static, data-free HTML shell (inline CSS/JS, no CDN) that lists runs paused at
an approval gate and lets an operator approve/reject them from a browser.

Security model (built against a dedicated security review):
- The shell is EXEMPT from auth (like /api/health) because a browser navigation
  carries no bearer header. It embeds ZERO run data and ZERO secrets.
- All data reads (/api/runs, /api/runs/{id}) and the mutation
  (/api/runs/{id}/approve) go through the EXISTING authed endpoints via fetch()
  with an operator-supplied bearer token held only in memory and sent only in
  the Authorization header — never in a URL or storage; cleared on unload.
- All dynamic DOM is built with textContent/createElement (never innerHTML), so
  a hostile run_id/step_id cannot inject markup or script.
- Approvals still require the per-gate S9 token, typed by the human.
- Response denies framing (X-Frame-Options: DENY + CSP frame-ancestors 'none')
  and caching (Cache-Control: no-store).

Scope: approve/reject only. Trigger-arbitrary-workflow is deliberately out of
scope (higher blast radius). +2 security tests (shell served without bearer /
carries no data / has headers; /api/runs still 401s without bearer).
Follow-up to the security re-review's one non-blocking suggestion: the console
response now sends a full CSP (default-src 'none'; style/script 'unsafe-inline';
connect-src 'self'; frame-ancestors 'none'; base-uri 'none') instead of just
frame-ancestors. The page is fully self-contained same-origin, so this only
denies exfiltration/injection avenues — no functional change. String-only edit;
the header test still asserts frame-ancestors is present.
@github-actions

Copy link
Copy Markdown

Bench compare

Threshold for regression: ≥ 10% slower mean.

Benchmark Mean change 99% CI
compile_crud_admin_template -1.27% [-2.70%, +0.07%]
compile_medium_program -2.28% [-3.60%, -1.12%]
compile_small_program -5.37% [-7.25%, -3.53%]
evidence_build_5_steps -26.32% [-28.96%, -23.47%]
evidence_build_empty -16.85% [-22.55%, -11.59%]
evidence_verify_10_steps -13.94% [-25.23%, -1.09%]
evidence_verify_5_steps +94.13% [+57.94%, +130.42%]
vm_call_dispatch_loop/iters=1000 +16.46% [+9.35%, +24.16%]
vm_call_dispatch_loop/iters=10000 -16.59% [-20.31%, -12.67%]
vm_pure_loop/iters=1000 -19.98% [-24.39%, -15.38%]
vm_pure_loop/iters=10000 -1.72% [-4.61%, +1.43%]
vm_pure_loop/iters=100000 -35.32% [-38.14%, -32.19%]
vm_record_loop/iters=1000 -31.75% [-35.32%, -27.79%]
vm_record_loop/iters=10000 -1.14% [-2.72%, +0.30%]

⚠️ 2 benchmark(s) regressed past threshold:

  • evidence_verify_5_steps
  • vm_call_dispatch_loop/iters=1000

@escapeboy
escapeboy merged commit ed0d99d into master Jul 17, 2026
4 of 5 checks passed
@escapeboy
escapeboy deleted the feat/mgmt-console branch July 17, 2026 15:33
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