Claude Code investigates a failed production deployment. It queries an MCP log tool and receives a stack trace containing a bearer token. The agent spots the token and removes it from the answer before handing the work to a reviewer.

The final answer is clean. The token is not.

It may now exist in the model request, the MCP gateway log, the tracing platform, a prompt cache, a tool error, and the review packet draft. Redacting the last copy does nothing to the copies already made.

This is an awkward failure because the agent can appear to behave responsibly. It notices the secret. It warns the operator. It prints [REDACTED]. Meanwhile, the observability system has preserved the original tool response for thirty days.

Sensitive data propagation receipt for Claude Code

Stop sensitive values before the model sees them

The strongest redaction point is the first trusted boundary after retrieval. For an MCP call, that is usually the gateway or tool adapter. Inspect the structured response there, before it enters the model context.

response_policy:
  tool: deployment_logs.read
  allowed_fields:
    - timestamp
    - service
    - error_code
    - sanitized_message
  blocked_classes:
    - credential
    - session_token
    - customer_personal_data
  replacement: safe_reference
  raw_retention: restricted_incident_store

Do not ask the model to decide whether a string is safe after you have already sent it the string. Pattern matching helps with familiar token formats, but classification should also use field names, source metadata, and data contracts. A value returned in authorization_header is sensitive even if it does not match the scanner’s current regular expressions.

The safe response can preserve useful evidence without exposing the value:

{
  "error_code": "DEPLOY_AUTH_401",
  "sanitized_message": "Bearer credential rejected",
  "sensitive_reference": "secret-event:7f92",
  "value_digest": "sha256:4a91...",
  "classification": "credential"
}

The reference lets an authorised incident responder retrieve the original from a restricted store. The digest lets the control plane determine whether two findings contain the same value. Claude Code gets enough information to diagnose the fault without receiving reusable authority.

Treat every copy as a separate disclosure surface

A production run has more data surfaces than the visible transcript. Inventory them before deciding that redaction works:

  • model request and response logs
  • MCP request and response logs
  • distributed traces and span attributes
  • prompt and semantic caches
  • retry queues and dead letter records
  • terminal output and shell history
  • review packets, tickets, and chat notifications
  • evaluator inputs and failure reports

Each surface needs an owner, a data policy, and a retention rule. Turning off prompt logging at the model provider does not protect a raw MCP response stored by your tracing library. Masking trace attributes does not remove a secret copied into an evaluator failure message.

This is why MCP tool output must remain evidence rather than authority. Tool output can contain hostile instructions, but it can also contain credentials and customer data. Both problems need enforcement before the content reaches later stages.

Carry a data label through the run

Once the gateway replaces a sensitive value, keep its classification attached to the safe reference. Downstream systems should make decisions from that label rather than rescanning a flattened string.

sensitive_item:
  reference: secret-event:7f92
  class: credential
  source: mcp:deployment_logs.read
  run_id: cc-run-4938
  first_seen_at: 2026-08-28T09:37:14Z
  allowed_destinations:
    - restricted_incident_store
  prohibited_destinations:
    - model_context
    - review_packet
    - analytics_export
  expires_at: 2026-08-29T09:37:14Z

A label can survive tool calls, retries, and handoffs. If a later component tries to place secret-event:7f92 in an analytics export, the policy engine can stop it even though the raw token is no longer visible.

Keep the raw store separate from ordinary agent telemetry. Access should use a named incident role, record the reason, and expire. The agent should never turn a safe reference back into the original value by itself.

Produce a propagation receipt

Before the run closes, reconcile where the sensitive item could have travelled. The receipt should name every expected surface and record whether it received raw, sanitized, or no data.

sensitive_data_propagation_receipt:
  run_id: cc-run-4938
  item_reference: secret-event:7f92
  classification: credential
  interception_point: mcp_gateway
  raw_value_reached_model: false
  surfaces:
    mcp_gateway_restricted_log: raw_encrypted
    model_context: safe_reference_only
    trace_platform: safe_reference_only
    prompt_cache: safe_reference_only
    review_packet: safe_reference_only
    evaluator: safe_reference_only
  deletion_actions: []
  unresolved_surfaces: []
  canary_leaks: 0
  status: verified

This artifact is more useful than secret redacted. It distinguishes prevention from cleanup. If the raw value reached a trace before the filter ran, record that surface under deletion_actions and keep the run open until the owner confirms removal or containment.

Some systems cannot guarantee selective deletion. Say so. Rotate the credential, shorten the affected retention period where possible, restrict access to the contaminated store, and preserve the incident record. A neat receipt must not pretend that a copy disappeared when the platform cannot prove it.

Add the receipt to the same review packet that carries the patch and test evidence. The packet should contain the safe reference and status, never the raw value.

Test with canaries, including the error paths

Seed disposable canary values into test tool responses. Give each surface a distinct canary so you can tell where a leak began.

fixtures:
  - name: token_in_structured_field
    expected: blocked_before_model
  - name: token_inside_stack_trace
    expected: replaced_with_safe_reference
  - name: scanner_times_out
    expected: fail_closed
  - name: retry_queue_copies_raw_response
    expected: test_failure
  - name: evaluator_prints_failed_input
    expected: sanitized_failure_record
  - name: review_packet_renders_tool_error
    expected: safe_reference_only

Test failures as aggressively as successful responses. Scanners time out. Parsers reject malformed JSON. Trace exporters retry old payloads. Debug modes get enabled during incidents, which is exactly when production logs are most likely to contain sensitive material.

Search every downstream test store for each canary after the run. Zero hits outside the restricted fixture store is the release condition. A final answer containing no canary proves only that one output path was clean.

My rule is simple: once Claude Code sees a raw secret, you have a containment problem rather than a formatting problem. Intercept sensitive values before model context, replace them with controlled references, and require a propagation receipt that accounts for every copy.

Claude Code: Building Production Agents That Actually Scale covers MCP boundaries, permissions, observability, evals, rollback, cost control, and review packets for production coding agents.