fix(vnc): reject native-VNC grants for sessions that are not live#89
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 19, 2026, 1:45 AM ET / 05:45 UTC. Summary Reproducibility: yes. in source and supplied endpoint evidence: a native-VNC-capable session in Review metrics: 2 noteworthy metrics.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the narrowly scoped live-status and post-mint identity guard if it remains clean against current Do we have a high-confidence way to reproduce the issue? Yes in source and supplied endpoint evidence: a native-VNC-capable session in Is this the best way to solve the issue? Yes. Matching the browser-VNC sibling’s pre-mint lifecycle check and post-mint access revalidation is a narrow, maintainable repair that preserves the existing native-VNC route contract for live sessions. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 753d9233abd3. Label changesLabel changes:
Label justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
Review history (4 earlier review cycles)
|
The native-VNC grant endpoint minted a desktop credential for any session whose capabilities advertised nativeVnc, with no check on the session's lifecycle status — unlike the browser-VNC path (InteractiveDesktopService.open), which rejects stopping/stopped/expired/failed before minting and re-validates after the mint. So a caller could obtain usable desktop credentials for a workspace that had already begun tearing down, and a concurrent stop racing the mint would leak a credential for a dead workspace. Extract the native-VNC path into a small NativeVNCService with injected deps (readSession, mintGrant) so it is unit-testable, and give it the same guards as its browser-VNC sibling: a pre-mint live-status allowlist (ready/attached/detached) and a post-mint re-validation that fails closed if the session began teardown during the mint round trip. Tests cover all three paths: mints for a live session, rejects a stopping session before minting, and fails closed when the session stops during the mint.
1d98f2b to
6103836
Compare
|
@clawsweeper re-review Added real production-endpoint proof: the fix runs under |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
What Problem This Solves
The native-VNC grant endpoint hands a caller usable desktop credentials for a session whose workspace
is already stopping or gone.
createNativeVNCGrant(inworker-application.ts) checked only that the session advertisesnativeVncbefore minting — it never checked the session's lifecyclestatus. The browser-VNC pathfor the same runtime-adapter workspace (
InteractiveDesktopService.open) does two things this pathdid not:
stopping/stopped/expired/failedbefore minting, andhasCurrentAccess), because the session canbegin teardown while the grant is being created.
So a client could obtain a working native-VNC credential for a workspace being torn down, and a stop
racing the mint would leak a credential for a dead workspace.
The change
Extract the native-VNC path into a small
NativeVNCServicewith injected deps (readSession,mintGrant) so it is unit-testable, and give it the same guards as its browser-VNC sibling:ready/attached/detached), andsession authorization changed; retry) if — during themint round trip — the session left a live state, control authorization was revoked, or its workspace
identity moved (control-plane, workspace id, provider resource, or profile). This mirrors the
browser-VNC sibling's
hasCurrentAccess, which re-checks control and the full workspace identityafter the mint, not just status. (
providerResourceIdin particular is rewritten by sessionreconciliation while a session stays live, so pinning it matters.)
worker-application.tsnow just wires the service in; no behaviour change beyond the added guards.Real production-endpoint proof (before → after)
The fix on the real HTTP endpoint: crabfleet running under
wrangler dev(workerd) with a reallocal D1 (SQLite), a real native-auth access token (bootstrap-owner identity), and real routing — not
a harness.
POST /api/native/v1/native-vncfor a seeded native-VNC session whose status isstopping:409 (refused at the guard)vs500 (reached the mint)on the production endpoint is the fix.Reproducible transcript + seed: https://gist.github.com/anagnorisis2peripeteia/d4f681075a24801417f293aed70c4b12
Service-level before/after (same two interleavings)
Driven through the extracted
NativeVNCService(base = the verbatim inline body fromworker-application.ts@f4ef10bL207-224, sinceworker-application.tscan't be imported undernode --test— its module graph pulls@cloudflare/containers, which is why the path wasextracted):
That the guard actually sits on the production path (and isn't just a property of a new class the
route could bypass) is pinned the way the repo's own
application-architecture.test.tspinscomposition — a source assertion that the
createNativeVNCGrantroute delegates toNativeVNCServicewith
readVisibleFresh+workspaceLifecycle().createNativeVNCGrant, and no longer mints inline.Testing
pnpm check(tsc + oxlint + oxfmt),pnpm build,pnpm exec wrangler deploy --dry-run,pnpm testall green (957 tests).
tests/native-vnc-service.test.tscovers: mints for a live session; rejects a stopping sessionbefore minting; fails closed when the session begins teardown during the mint; rejects a
missing/uncontrollable session; rejects ineligible sessions (asserting the mint is never reached on
any rejection path); fails closed when control is revoked or the control-plane is re-bound during the
mint; when the provider resource or profile changes during the mint; and the production-wiring
assertion above. Mutation testing of the new service scores 87% (74/85). A concurrency lint of the
diff (a race/leak static pass) is clean.
Related: FINDING 1 of #88 (a small set of lifecycle concurrency findings; separate PRs to follow).
Review history: https://gist.github.com/2f7c872efcad9a64122db222f4a54e67