Summary
BUZZ_PRIVATE_KEY is the only way to give an agent identity its signing key. Both entry points take it as a clap arg with an env fallback and nothing else:
crates/buzz-cli/src/lib.rs:86 — #[arg(long, env = "BUZZ_PRIVATE_KEY", hide_env_values = true)] private_key: Option<String>
crates/buzz-acp/src/config.rs:243 — #[arg(long, env = "BUZZ_PRIVATE_KEY", hide_env_values = true)] pub private_key: String
A search of the tree finds no file-based, keyring-based or systemd-credential source: no BUZZ_PRIVATE_KEY_FILE, no CREDENTIALS_DIRECTORY / LoadCredential handling anywhere in the repo.
This is complementary to #2883, not a duplicate: that issue is about not handing the raw key to model-controlled processes (MCP config, shell children). This one is about how the harness itself receives the key in the first place — and its "additional threat-model note" explicitly leaves this to a separate issue.
Why it matters on a multi-agent host
The natural way to run several agent identities on one VPS is a .env per agent, exported into each unit or shell. That makes the key a plain string in a shared namespace, with two consequences:
- Any process that can read another agent's
.env can sign as that agent. Nothing in the protocol can distinguish it: the signature is valid, and a NIP-OA auth tag attests who owns an agent, not which agent is speaking. We hit exactly this — a session on our host published relay events signed as a different agent because that agent's key was still exported in the environment it inherited. Recovering that it happened at all required reading the events afterwards.
--private-key is worse than the env var and looks equivalent. As a flag the key lands in /proc/<pid>/cmdline, world-readable to every user on the box; hide_env_values = true only affects --help output. Nothing warns about the difference at the point of use.
Neither of these is exotic — one host, several agents, one operator is the shape of every self-hosted deployment we've seen discussed in the issue tracker.
Proposal (small, additive)
- Accept
BUZZ_PRIVATE_KEY_FILE=/path/to/key (hex or nsec, trailing newline trimmed) in both buzz-cli and buzz-acp, taking precedence over BUZZ_PRIVATE_KEY. The file can then be 0600 root:agent-x, so OS permissions — not shell hygiene — decide who can sign.
- Optionally honor systemd's
CREDENTIALS_DIRECTORY (LoadCredential=buzz-key:/etc/buzz/agent-x.key), which is the idiomatic form of the same thing for the unit-file deployments the docs already recommend.
- Keep
BUZZ_PRIVATE_KEY working unchanged as the fallback; note in --help that --private-key exposes the key through ps.
Precedence would be: --private-key → BUZZ_PRIVATE_KEY_FILE → BUZZ_PRIVATE_KEY → credential directory, or whatever order maintainers prefer — the ordering is the only real design question here.
Acceptance criteria
- Starting
buzz-acp and buzz with only BUZZ_PRIVATE_KEY_FILE set authenticates and publishes normally.
- A malformed/unreadable file fails at startup with a clear error and never falls through to an unauthenticated session.
- The key never appears in the process's own argv when supplied by file.
- Tests cover hex and
nsec file contents, trailing whitespace, and the precedence order.
Happy to send the PR if maintainers confirm the shape and the precedence order they want.
Summary
BUZZ_PRIVATE_KEYis the only way to give an agent identity its signing key. Both entry points take it as a clap arg with an env fallback and nothing else:crates/buzz-cli/src/lib.rs:86—#[arg(long, env = "BUZZ_PRIVATE_KEY", hide_env_values = true)] private_key: Option<String>crates/buzz-acp/src/config.rs:243—#[arg(long, env = "BUZZ_PRIVATE_KEY", hide_env_values = true)] pub private_key: StringA search of the tree finds no file-based, keyring-based or systemd-credential source: no
BUZZ_PRIVATE_KEY_FILE, noCREDENTIALS_DIRECTORY/LoadCredentialhandling anywhere in the repo.This is complementary to #2883, not a duplicate: that issue is about not handing the raw key to model-controlled processes (MCP config, shell children). This one is about how the harness itself receives the key in the first place — and its "additional threat-model note" explicitly leaves this to a separate issue.
Why it matters on a multi-agent host
The natural way to run several agent identities on one VPS is a
.envper agent, exported into each unit or shell. That makes the key a plain string in a shared namespace, with two consequences:.envcan sign as that agent. Nothing in the protocol can distinguish it: the signature is valid, and a NIP-OA auth tag attests who owns an agent, not which agent is speaking. We hit exactly this — a session on our host published relay events signed as a different agent because that agent's key was still exported in the environment it inherited. Recovering that it happened at all required reading the events afterwards.--private-keyis worse than the env var and looks equivalent. As a flag the key lands in/proc/<pid>/cmdline, world-readable to every user on the box;hide_env_values = trueonly affects--helpoutput. Nothing warns about the difference at the point of use.Neither of these is exotic — one host, several agents, one operator is the shape of every self-hosted deployment we've seen discussed in the issue tracker.
Proposal (small, additive)
BUZZ_PRIVATE_KEY_FILE=/path/to/key(hex ornsec, trailing newline trimmed) in bothbuzz-cliandbuzz-acp, taking precedence overBUZZ_PRIVATE_KEY. The file can then be0600 root:agent-x, so OS permissions — not shell hygiene — decide who can sign.CREDENTIALS_DIRECTORY(LoadCredential=buzz-key:/etc/buzz/agent-x.key), which is the idiomatic form of the same thing for the unit-file deployments the docs already recommend.BUZZ_PRIVATE_KEYworking unchanged as the fallback; note in--helpthat--private-keyexposes the key throughps.Precedence would be:
--private-key→BUZZ_PRIVATE_KEY_FILE→BUZZ_PRIVATE_KEY→ credential directory, or whatever order maintainers prefer — the ordering is the only real design question here.Acceptance criteria
buzz-acpandbuzzwith onlyBUZZ_PRIVATE_KEY_FILEset authenticates and publishes normally.nsecfile contents, trailing whitespace, and the precedence order.Happy to send the PR if maintainers confirm the shape and the precedence order they want.