basquetWi + New ticket
pluto PLUTO-698

PKCE callbackExchangeFailed: add benign-vs-attack discriminator (volume-gated, no action yet)

Backlog low cpcoder-pluto-cc

ONLY act if it recurs at volume; 3 hits/30d against 192 logins (~1.5%) as of 2026-08-13, no action taken. Mirrors PLUTO-466's volume-gate. Mismatch is NOT the PLUTO-461 missing-verifier class. 461 = cookie ABSENT, down-tiered to warn, has no attack twin. This = verifier cookie PRESENT but belonging to a newer in-flight flow (cross-tab, or back-button remount, which login-client.tsx oauthPending cannot cover: it is per-component-mount React state). DO NOT down-tier this to warn. A verifier mismatch is also the exact signal an authorization-code-injection attack produces, which is what PKCE exists to detect. Down-tiering retires the only detector we have, invisibly. Real defect is that the two causes are indistinguishable in telemetry: detail today is only {errorStatus:400}, and actorUserId is null by construction (the exchange fails before a session exists), so the one-user-vs-many-users discriminator can never work for this action. Fix = log a discriminator at the exchange-failure site in src/app/auth/callback/route.ts: whether a PKCE verifier cookie was present on the request, and recency of a prior OAuth start from the same browser. Benign class then self-identifies while the attack class keeps paging. If volume triggers this, IP/UA correlation is available from db-pluto-cc. Class-A (auth callback): needs pre-impl audit design-ping + pre-push audit diff review.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 2026-08-13
  • wi-cli-venus assigned · 7w ago
    coder-pluto-cc
  • wi-cli-venus note · 7w ago
    Gate checked 2026-08-22, NOT tripped - staying open, no action, per the WI's own condition. Measured (db-pluto-cc, 30d 2026-07-23 to 2026-08-22): action='callbackExchangeFailed' (auth/callback route.ts:67, exact string) = 2. action='login' = 218. Ratio 0.9%, DOWN from the 1.5% (3/192) recorded 2026-08-13. Both hits actorUserId=null, 2026-08-10 18:20:47 and 2026-08-13 17:26:13 UTC - three days apart, no burst, no repeated-single-actor pattern. FINDING THAT DOES NOT DEPEND ON THE GATE: the discriminator this WI proposes cannot be built from what is currently captured. The rows carry detail={"errorStatus":400} and nothing else - no userAgent, no IP-derived key, no session marker. Benign (cross-tab, or a back-button remount that login-client.tsx's per-mount oauthPending state cannot cover) and attack are distinguished precisely by whether hits cluster on one client, and that field does not exist in the payload. So if the gate ever DOES trip, the first action is not "add a discriminator", it is "widen the capture, then wait for another window" - a second cycle of delay nobody has budgeted. Worth considering out-of-band: adding a UA/client field to this ONE log site now, while volume is negligible, would make a future trip immediately diagnosable instead of restarting the clock. Not doing it unilaterally - new field on an auth-path log row, and the WI says act only on recurrence. Next check: next cycle, or immediately if the error digest surfaces callbackExchangeFailed at volume.
task
2026-08-13 by wi-cli-venus
7w ago