redact encrypted_content in traffic captures - #153
Merged
Merged
Conversation
Codex server-side compaction returns an opaque encrypted_content blob that the proxy replays as a compaction input item on every later turn. With CCP_TRAFFIC_LOG=1 that blob was written to disk verbatim in the captured upstream request body and in the per-event response captures. The blob is ciphertext, so this is not a plaintext leak, but it is a large, replayable handle to a whole conversation's context and it bloats captures considerably. image_url and Anthropic source.data are already redacted for the same reason: bulk conversation payload does not belong in a debug capture. Add encrypted_content to the bulk-payload arms of redact_traffic rather than to REDACT_KEYS. REDACT_KEYS is shared with the structured logger, which only ever receives request metadata, never bodies, so the key cannot reach it; keeping the change in the traffic path avoids widening a credential list with something that is not a credential. The value is replaced with the existing "[redacted len=N]" form so blob size remains visible for diagnostics. Reasoning items carry the same key and are covered by the same arm. This covers 020-upstream-request and the 040/050 JSON event captures. The raw 032-upstream-response-body.sse capture on the Codex HTTP and WebSocket paths is written with write_bytes and bypasses redaction entirely, so the blob still persists there; that path is left as is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The encrypted_content redaction arm only recognizes the key `encrypted_content`. The same opaque conversation handle also reaches traffic captures inside the Anthropic thinking signature the Codex translator emits, `ccp:codex:v1:<encoded-id>:<encrypted_content>`: streaming writes it under `delta.signature` in 050 downstream events, buffered translation writes it under `content[].signature`, and the next turn replays the same value through the 010 incoming Anthropic request capture. A capture could therefore still contain the full replay blob while the key-based redaction passed its tests. Add a `signature` arm that redacts the whole value only when it is proxy-owned. Ownership is decided by `is_proxy_reasoning_signature`, added beside the encoder so the `ccp:codex:v1:` format has a single source of truth instead of a copied prefix. Foreign signatures, such as real Anthropic signatures, are left untouched. The value is replaced with the existing `[redacted len=N]` form so blob size stays visible, matching the neighboring bulk payload arms. Tests cover a 050 signature delta, buffered and incoming thinking blocks, unrelated signature preservation, and the actual capture boundary through write_json_event and write_json. Raw SSE and native raw response bytes still bypass redaction; that pre-existing gap is unchanged and remains separately tracked.
Bring the prepared raine#153 branch onto local main 0e9a6aa, which carries the merged grok streaming corrections and the cold smoke timeout fix. The original PR raine#153 head d4452cb and the traffic correction 48adf80 remain ancestors, so the contributor branch still fast-forwards without a rebase or force push. The merge is clean and drops nothing from either side. Main did not touch the files this branch changed, so the JSON signature redaction correction is unchanged and the combined tree adds everything main gained after the branch base bcfa0a4.
Owner
|
Thanks for the redaction fix! Merged, including the reasoning-signature capture case. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Codex server-side compaction returns an opaque
encrypted_contentblob, which the proxy replays as acompactioninput item on every later turn. WithCCP_TRAFFIC_LOG=1that blob is written to disk verbatim.To be clear about severity: the blob is ciphertext we cannot read locally, so this is not a plaintext leak. But it is a large, replayable handle to a whole conversation's context, and because it is re-sent every turn it accumulates across a session's captures.
image_urland Anthropicsource.dataalready have explicit arms inredact_trafficfor the same reason — bulk conversation payload does not belong in a debug capture. This applies the existing precedent toencrypted_content.Fix
A new arm in
redact_traffic_with_depth, next to thesource.datacase, routing the value through the existingredact_traffic_valueso it becomes[redacted len=N]. Blob size stays visible, since that is genuinely useful diagnostic information.Not added to
REDACT_KEYS, for two reasons:REDACT_KEYSis shared with the structured logger vialogging::redact_value, which is only ever called on thefieldsmap inLogger::emit. No body, input item, or event value reaches the logger, so adding the key there would be a no-op for logging.REDACT_KEYSis a credential list. This is bulk payload, which belongs with theimage_url/source.dataarms.Reasoning items carry the same key and are covered by the same arm.
Scope — what this does not cover
This covers
020-upstream-request(write_json) and the040/050per-event JSON captures (write_json_event).It does not cover the raw
032-upstream-response-body.ssecapture on the Codex HTTP and WebSocket paths (client.rs,websocket.rs), which is written withwrite_bytesand bypasses redaction entirely. The blob still persists there. Fixing that means either re-serialising parsed events or a text-level scrub of the SSE, which felt like a separate change — happy to follow up if you'd prefer it in one go.Providers that assemble
upstream_ssefromStreamTrafficCapture::upstream_event(grok, opencode) are already redacted, since those frames go throughwrite_json_event. Codex is the one path that dumps raw bytes.Verification
redact_traffic_strips_compaction_encrypted_content— acompactionitem in a request body; asserts the blob is replaced by[redacted len=N], thegAAAAAprefix is gone, andtype, message text andmodelsurviveredact_traffic_strips_reasoning_encrypted_content_in_events— same for a reasoning item insideresponse.output_item.donecargo clippy --all-targets -- -D warningsclean,cargo fmt --checkclean, full suite green🤖 Generated with Claude Code