Skip to content

buzz-acp: redact MCP env secrets from wire logs and observer feed - #3417

Open
Maxwellimus wants to merge 1 commit into
mainfrom
maxwell/acp-redact-wire-secrets
Open

buzz-acp: redact MCP env secrets from wire logs and observer feed#3417
Maxwellimus wants to merge 1 commit into
mainfrom
maxwell/acp-redact-wire-secrets

Conversation

@Maxwellimus

Copy link
Copy Markdown

Summary

The ACP harness injects the agent's Nostr secret key as BUZZ_PRIVATE_KEY — an MCP-server env var in the session/new config. That wire message (and any message echoing it back) was serialized verbatim into the acp::wire debug log and published to the in-process observer feed, so the secret appeared in cleartext in both sinks.

This masks MCP-server env values in the logged/observed copies only. The message delivered to the agent over stdin is unchanged — the agent needs the real key to authenticate.

Scope

The observer feed is owner-scoped and the acp::wire debug log is off by default, so this is secret-hygiene / defense-in-depth rather than a broadly reachable leak. Secrets still shouldn't land in logs or an observability stream, and an agent echoing its config back would surface the key wherever those sinks are read.

Change

  • redact_wire returns a copy with every env[].value masked (<redacted>), recursively — covering both the outbound params.mcpServers and any inbound echo. Names and all other fields are preserved. Borrows (no clone) when there's no env to mask.
  • Applied at every log/observe sink: outbound session/prompt, session/new, notifications, and session/steer; and inbound reads in both read loops.
  • Parse-error events publish only the failed line's length, not its content — an unparseable line can't be structurally redacted and could be a malformed echo of the config.
  • write_ndjson still delivers the unredacted message to the agent.

Tests

  • Config redaction (e2e): drives the real session/new path with a fake agent that echoes the request back. Asserts the agent receives the real key, the secret appears in no observer event, and both the outbound write and the inbound echo are masked.
  • Parse-error path (e2e): a malformed secret-bearing line is recorded as a parse error but never surfaces the secret in the feed.
  • Unit: redact_wire masks values while preserving shape (built from the real McpServer/EnvVar structs so a serde rename can't silently disable it) and is a no-op for messages without MCP servers.

just ci green.

🤖 Generated with Claude Code

@Maxwellimus
Maxwellimus requested a review from a team as a code owner July 28, 2026 22:24
The ACP harness injects the agent's Nostr secret key as BUZZ_PRIVATE_KEY
(an MCP-server env var in the session/new config). That wire message, and
any message echoing it back, was serialized verbatim into the acp::wire
debug log and the in-process observer feed, exposing the key in cleartext
in both sinks.

Mask MCP-server env values in the logged/observed copies only; the message
delivered to the agent over stdin is unchanged (the agent needs the real
key to authenticate). redact_wire masks env[].value recursively, covering
the outbound config and any inbound echo, and borrows when there is no env
to mask. Parse-error events publish only the failed line's length, since an
unparseable line can't be structurally redacted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Max Lampert <maxwell@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant