block / block/buzz

security: agent signing keys can only be supplied via BUZZ_PRIVATE_KEY (no file, keyring or systemd-credential source)

Open
#5,568 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## 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`
- `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//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)

1. 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.
2. 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.
3. 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.