# Critique of Candidate A

WEAKNESS-1 [FATAL]: "WebSocket upgrade: Accepts ONLY `Authorization: Bearer`, never cookie, never `?token=`. Codex sets this when launched via ccxray's launcher." — Browsers cannot set arbitrary headers on WebSocket handshakes via the `WebSocket` constructor. The dashboard's own browser-side code therefore cannot authenticate any WS upgrade under this rule. The candidate hand-waves "Codex sets this" but says nothing about how the dashboard UI itself opens WS connections (e.g., for live streaming features), and the threat model explicitly distinguishes "browser" from "CLI." Either the dashboard has no WS (in which case why specify this gate at all?) or the rule is unimplementable for the dashboard — the design never resolves which.

WEAKNESS-2 [FATAL]: "CLI/curl: Bearer authenticated. Exempt from Origin/Host CSRF check because bearer cannot be sent by a browser victim cross-origin without explicit JS that must already have the bearer (OWASP custom header pattern)." — This is wrong about the bearer header and wrong about the Host check. (a) The Host header IS sent by browsers and a DNS-rebind attacker controls it; exempting bearer-auth requests from the Host check leaves an open hole for any victim who has the bearer cached in a browser extension, password manager autofill, or a service worker — and more critically, exempts the very requests Codex/Claude Code send through localhost from rebind protection. (b) Saying "Host check enforced" for upstream-proxy in §2 directly contradicts "Origin check skipped for upstream-proxy" combined with the table in §4 claiming Host allowlist defends rebind — the candidate states both that Host is checked and that bearer requests are exempt from CSRF checks (which the §3 text bundles together as "for every cookie-auth'd request AND every non-GET regardless of auth method"). Which is it?

WEAKNESS-3 [FATAL]: "User opens `http://localhost:5577/?token=<AUTH_TOKEN>`. Server 302s to `/_auth?token=<AUTH_TOKEN>&next=/`." — The token is now leaked into the server access log, the browser history, the Referer header of any resource the redirect target loads (Referrer-Policy on `/_auth` is too late — the leak happens on the inbound `/` request, and the redirect chain itself is logged), and any process listing observing the URL bar. The candidate claims "Token in URL exactly once at `/_auth`" but in fact the token appears at `/?token=`, then `/_auth?token=`, i.e., twice, and the first hop has no Referrer-Policy control because it's the entry point. The mitigation row #3 is materially false.

WEAKNESS-4 [MAJOR]: "stores `sha256(S)` in in-memory `Set<string>` with expiry now+8h" — Storing in a `Set<string>` has no expiry mechanism; Sets don't evict. The text says "with expiry now+8h" but never describes the sweep. With an 8h window and unbounded entries (every reload of an expired bookmark redeems a new session), the Set grows monotonically until restart. Worse, "Server restart: in-memory sessions wiped" means every hub idle-shutdown (5s per the project notes) invalidates all browser sessions — this directly contradicts the smooth-UX claim and makes the cookie approach near-useless in hub mode where idle shutdown is the norm.

WEAKNESS-5 [MAJOR]: "Hub mode: hub holds canonical session set, clients proxy through hub. New hub on crash recovery generates fresh ipcSecret and fresh session set." — This is incompatible with the project's documented hub model where `~/.ccxray/hub.json` is the discovery file and clients connect directly to the hub port. If the hub holds the session set, then a crash-recovery fork (per the project notes: clients monitor pid every 5s and fork a new hub) means every browser session dies every time any client restarts the hub. Combined with idle shutdown after 5s, the cookie has an effective lifetime measured in seconds, not 8 hours. The "Max-Age=28800" is theater.

WEAKNESS-6 [MAJOR]: "SameSite=Strict + Origin check on non-GET (defense in depth)" + "Origin check skipped for upstream-proxy" — The upstream-proxy path is exactly where state-changing requests to Anthropic go (POST /v1/messages, etc.). If the classifier routes a request to `upstream-proxy` based on path, an attacker who controls a victim's browser and knows the bearer (or can ride a cookie session, since the unified middleware "accepts either the bearer header or the cookie") can POST to `/v1/messages` cross-origin with no Origin check. The candidate's own unified middleware undermines the CSRF claim: cookie auth + upstream-proxy classification + Origin skip = CSRF-able expensive API calls billed to the user.

WEAKNESS-7 [MAJOR]: "The CLI launcher injects `ANTHROPIC_AUTH_TOKEN=<AUTH_TOKEN>` as a request header via the launcher's env." — `ANTHROPIC_AUTH_TOKEN` is the Anthropic API credential variable; conflating it with ccxray's `AUTH_TOKEN` means either (a) the proxy's shared secret is now identical to the user's Anthropic key, which is a credential-coupling disaster, or (b) the launcher is overwriting the user's real Anthropic token with ccxray's shared secret, breaking upstream auth. The candidate does not say which. Codex's `-c request_headers` claim is similarly hand-waved — no actual header name is specified for the bearer injection into Codex's HTTP layer, and Codex's WS transport (per the project notes, the main session uses WS on `/v1/responses`) cannot accept arbitrary headers from `-c` config in the way implied.

WEAKNESS-8 [MAJOR]: "treats absence of `AUTH_TOKEN` as '127.0.0.1-only, anonymous OK'" combined with "`AUTH_TOKEN` unset + bound to 0.0.0.0 now requires explicit `CCXRAY_ALLOW_ANONYMOUS_REMOTE=1` opt-in." — Any local process on a multi-user machine (shared dev box, CI runner, devcontainer with other tenants) can hit 127.0.0.1:5577 and exfiltrate every recorded request/response, including the user's Anthropic key in captured headers. "127.0.0.1-only" is not a security boundary on shared hosts, and the candidate elsewhere acknowledges XSS-in-conversation as a vector — meaning conversation content can include hostile data — yet the default posture exposes everything to any local UID with zero auth.

WEAKNESS-9 [MINOR]: "tests: 3 new files (auth-cookie, auth-csrf, auth-rebind), <80 lines each" — 240 lines of tests for a security middleware that handles bearer, cookie, Origin, Host, WS upgrade, hub IPC, anonymous mode, constant-time comparison, session expiry, and rotation is wildly under-scoped. The candidate offers a line budget instead of a coverage matrix; this is a tell that the design hasn't been mentally executed against its own threat table.

WEAKNESS-10 [MINOR]: "mismatched length runs fake hash before short-circuit" — Performing a "fake hash" before short-circuit does not equalize timing meaningfully unless the fake work is calibrated to the real work's distribution. `timingSafeEqual` requires equal-length buffers; the actual safe pattern is to hash both sides to a fixed-width digest and compare those. The candidate's phrasing reveals a misunderstanding of where the timing channel lives.

VERDICT: The design's CSRF/rebind defense collapses because the unified middleware exempts bearer/upstream-proxy from Origin checks while accepting cookies on the same paths, and the cookie session lifetime is contradicted by the project's hub idle-shutdown and crash-recovery behavior — making both the threat-model claims and the UX claims false.
