Repository navigation
fix(responses): redact terminal reset-replacement errors - #6616
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (9)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
✅ Deterministic PR hygiene checks passed. |
Terminal reset-replacement diagnostics could still carry the selected credential in two forms the bounded mask missed: an unresolved named HTML reference (which folds to an opaque placeholder of unknown decoded width, e.g. + or fj) and a JSON number/boolean/null whose text equals the credential. Withhold a diagnostic whose fold contains the placeholder, and redact matching scalars while unmatched scalars keep their types. Regression cases cover both adapters in the existing confidentiality block.
The bounded credential matcher decodes JSON \u escapes, percent escapes, lowercase-x numeric references and semicolon-terminated named references. Other forms (JSON short escapes such as \/, uppercase-X or unterminated numeric references, legacy named references without a semicolon) stayed literal, so a credential spelled through them could not be matched. When such syntax remains in the final matching view, withhold the diagnostic. Unrelated query strings (a=1&b=2) are preserved.
…gnostics Enumerating semicolon-less HTML references left gaps (a legacy & before letters or "=", or a long run). Withhold the terminal diagnostic whenever an '&' followed by a letter or '#', or a JSON short escape, remains in the final matching view. Over-withholding is accepted on this terminal-only path. Adds both-adapter coverage for malformed and over-nested JSON, array bodies, non-2xx SSE, and dropped upstream headers; records the policy in the responses structure doc.
…ding A bounded decoder cannot be proven equivalent to every client decoder; review found a truncated UTF-8 percent sequence that folded differently from decodeURIComponent. On the terminal non-replayable projection, withhold any diagnostic string containing percent, JSON-escape or HTML reference syntax (checked before and after a lookalike-only fold with escape decoding off). Plain and lookalike echoes are still masked in place. Reverts the NAMED_ENTITY_PLACEHOLDER export, which no longer has a consumer.
Review found Unicode lookalikes (uppercase Greek alpha, ligatures, zero-width splits) that the confusable fold maps differently from the credential, leaving a visually recoverable credential. No fold can be proven equivalent to a reader, so the terminal projection now withholds any diagnostic string containing encoding syntax or a character outside printable ASCII, and otherwise relies on exact replacement of the selected credential.
Five review rounds each found another way a selected credential survived redaction while upstream diagnostic text was preserved (encodings, Unicode lookalikes, HTML markup, JSON number normalization, marker overlap). No bounded redaction can be proven complete against a client's decoders, so the terminal non-replayable projection now forwards no upstream body text: it keeps the real status, non-replayability, an allowlisted error type and x-should-retry: false, and returns a fixed message. The shared credential mask in terminal-error-redaction.ts returns to its dev form. The confidentiality matrix covers both adapters with every hostile diagnostic shape found in review and asserts the exact generic body, two physical sends, and the spent replacement grant.
…ojection The combo owner reformats a consumed child failure, so the exact generic body is not the combo contract; assert the generic phrase survives, the upstream diagnostic and credential do not, and the terminal stop keeps two sends.
Independent security review — head
|
…ror code Responses are marked non-replayable by more than reset replacements (WS socket death, compaction, unsafe run-turn activity), so the withheld-text message now says "for a non-replayable failure". A closed allowlist of machine error codes (context_length_exceeded, rate_limit_exceeded, ...) is kept alongside the allowlisted type so clients retain an actionable signal. Updates the two hosted-CI failures that asserted relayed upstream text on marked responses (console-go upload boundary, WS pool-mode replacement 400).
Independent security review — head
|
|
Maintainer integration (lidge-jun, dev only) — 2026-10-05 bug triage round 2, lane B4.
|
Summary
When a connection-reset replacement (from #6555), or another send marked non-replayable, gets a real upstream error back, the generic/native Responses dispatch path returns it before the normal client error projection runs. Upstream diagnostic text on that path could echo the selected credential back to the client.
This PR makes that terminal path forward no upstream body text or headers. The client receives:
typetaken only from a closed allowlist (invalid_request_error,rate_limit_error, …), otherwiseupstream_error, pluscodewhen it exactly matches a closed allowlist of machine codes (context_length_exceeded,rate_limit_exceeded, …);x-should-retry: falsewhen upstream sent exactly that;Provider error <status>: upstream diagnostic withheld for a non-replayable failure.Synthetic replay refusals and ordinary (unmarked) error responses keep their existing delivery owners. The shared credential mask in
terminal-error-redaction.tsis unchanged fromdev. Owner docs updated in place:structure/transports/responses.mdandstructure/transports/responses-failover.md.Design note: an earlier version of this PR tried to keep the diagnostic and redact the credential from it. Independent security review found five successive bypasses of that approach (unresolved and semicolon-less HTML references, JSON short escapes, truncated UTF-8 percent sequences, Unicode lookalikes, HTML markup splitting, JSON number normalization). Withholding the text is the only option on this rare terminal path that can be shown complete.
Verification
Head:
0ceb2fbdd8f2eb76e6cbd1da74ed195667cdb295, merged withdev(merge-tree clean against the currentdevtip).ci: success) and React Doctor 37336122076: success.bun run typecheck,bun scripts/file-size-ratchet.ts,bun run structure:check,bun run privacy:scan,git diff --check. Both structure docs stay at 600 lines.openai-chat,openai-responses), intests/responses/responses-translated-reset.test.ts: a hostile-diagnostic matrix covering every bypass shape found in review (plus plain echo, scalars, JSON keys, arrays, non-2xx SSE, malformed and over-nested JSON) asserting the exact generic body, two physical sends, the spent grant, and that the credential is absent; allowlisted type and code preservation; unapproved and credential-valued type/code rejection; header allowlist; OAuth replacement; combo terminal consumption; marker provenance; bodyless and cancelled reads. Two existing tests that asserted relayed upstream text on marked responses were updated to the new contract with their send/replay assertions unchanged (responses-console-go-upload-retry.test.ts,ws-ambiguous-resend.test.ts).Checklist
Review readiness checklist
Summary by CodeRabbit
Bug Fixes
Documentation