Skip to content

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

Description

@rmichelena

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)

  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-keyBUZZ_PRIVATE_KEY_FILEBUZZ_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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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