Skip to content

buzz --help renders BUZZ_PRIVATE_KEY and BUZZ_AUTH_TAG values in plaintext (fix: hide_env_values) #3629

Description

@HMLauritzen

Summary

Top-level buzz --help renders the values of BUZZ_PRIVATE_KEY and BUZZ_AUTH_TAG in its help text, not just the variable names. Any agent or user who runs buzz --help with those variables set writes its full signing key and its full owner attestation to stdout — and therefore into whatever captures that output.

This is clap's default behaviour (env-var values are shown in [env: VAR=value]), so the arg definitions simply need hide_env_values = true.

Affected surface (measured, values redacted)

Running buzz --help with a managed agent's environment set:

BUZZ_PRIVATE_KEY : rendered, 63-char value (full nsec1…)
BUZZ_AUTH_TAG    : rendered, 208-char value (full NIP-OA attestation JSON)
BUZZ_RELAY_URL   : rendered

Scoped precisely: only the top-level help does this. buzz messages --help, buzz repos --help, and buzz messages send --help render neither value. The global options block is the leak point.

Version: CLI shipped in Buzz Desktop, /Applications/Buzz.app/Contents/MacOS/buzz, build dated 2026-07-29.

Why the two values together matter

A leaked private key alone is inert on a membership-gated relay: a plain NIP-42 kind:22242 AUTH from a managed-agent key is rejected with restricted: not a relay member, because managed-agent membership rides on the NIP-OA auth attestation tag. --help discloses both halves at once, which is what makes the output a usable identity rather than a useless secret.

Impact

Local information disclosure (CWE-532, sensitive information written to log/output). Not remotely exploitable on its own — an attacker needs read access to the captured output. Severity therefore depends entirely on where agent output lands, and in the shipped configuration that turns out to be the problem:

  1. Agents are instructed to run it. The managed-agent base prompt says, verbatim: "Run buzz --help or buzz <group> --help for full usage." So the leak is not an accident a careful operator can avoid — it is the documented first step, executed on fresh sessions, unattended.

  2. Harness transcripts persist it. With the Claude Code runtime, agent sessions are recorded to ~/.claude/projects/<slug>/*.jsonl. Those files are mode 0600, but every managed agent on the machine runs as the same user, so any agent can read every other agent's transcript, including the keys in them.

  3. It recurs. On one deployment I inventoried four transcript files containing key material across two agents. After deleting all four and verifying zero remained, a new one appeared 25 minutes later — a different agent session had run buzz --help again. Cleanup does not converge while the flag is unset.

Worth noting in contrast: Buzz's own storage is careful. Private keys are in the macOS Keychain, absent from managed-agents.json; agent logs and retention DBs contained zero key material. The leak is entirely via help output into the layer above.

Reproduction

# with a managed agent's env set
buzz --help | grep -A2 'private-key'
# → [env: BUZZ_PRIVATE_KEY=nsec1…<full 63-char key>]
buzz --help | grep -o 'BUZZ_AUTH_TAG=.*'
# → full attestation JSON

To confirm without staining your own terminal history, redirect to a file and measure lengths rather than printing.

Suggested fix

hide_env_values = true on both args:

#[arg(long, env = "BUZZ_PRIVATE_KEY", hide_env_values = true)]
private_key: Option<String>,

#[arg(long, env = "BUZZ_AUTH_TAG", hide_env_values = true)]
auth_tag: Option<String>,

Two adjacent hardening items, lower priority but same root concern:

  • --private-key / --auth-tag as CLI flags put secrets in argv, visible to any same-user process via ps. Consider accepting them only via env or a file path (--private-key-file), or documenting the env var as the supported path.
  • A grep for other secret-bearing args with env = … would be worth doing at the same time; I only audited these three variables.

What I did not verify

  • Whether other runtimes (codex, goose) persist agent output the same way — I confirmed the Claude Code path and found ~/.codex clean on this machine.
  • Whether this has already been reported privately; the public issue tracker has nothing matching, but I can't see private reports.

Disclosure note

I tried the private vulnerability reporting channel first — GET /repos/block/buzz/private-vulnerability-reporting returns {"enabled":true}, but POST /repos/block/buzz/security-advisories/reports returns HTTP 500 with an empty body, reproducibly, including with a minimal {summary, description} payload. (A deliberately invalid severity was correctly rejected with a 422, so the request is reaching validation — the 500 is server-side.) Your private channel is advertised but not functioning; worth checking independently of this report.

Filing publicly instead, on the reasoning that this has no remote vector: anyone positioned to read the help output already has local access to the agent's environment, and therefore to the key regardless. The novel harm here is persistence into files that outlive the process and are readable across agents — which is what the fix addresses.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions