Skip to content

Commit 28b0823

Browse files
BradGrouxBrad Groux
authored andcommitted
Address P0/P1 review feedback: credential isolation, fail-closed, name sanitization
Four issues from wesbillman/Carl's review: P0 — Extra MCP processes received Buzz identity credentials despite the PR's isolation claim. build_mcp_servers gave extras an empty per-server env, but buzz-agent's spawn_one cleared and repopulated every MCP child's environment from PASSTHROUGH_ENV, which includes BUZZ_PRIVATE_KEY, BUZZ_RELAY_URL, and BUZZ_AUTH_TAG. Added a trusted flag to McpServer and McpServerStdio; spawn_one now withholds identity credentials from untrusted servers. The primary buzz-dev-mcp server is trusted; extras are not. P1 — BUZZ_ACP_EXTRA_MCP_COMMANDS was absent from Desktop's reserved env key list, allowing a portable persona or per-agent env to inject an arbitrary command. Added to reserved_env_keys.rs and its test. P1 — Malformed quoting logged the raw command (which may contain an embedded API key) and silently skipped the entry. Now fails closed with only the entry index; the raw command is never echoed. P1 — Generated names used the executable stem verbatim, violating McpRegistry's ASCII alphanumeric/hyphen and 128-byte contract. Added sanitize_mcp_name to replace non-conforming characters with hyphens, strip leading/trailing hyphens, and truncate to 128 bytes. Co-authored-by: Brad Groux <brad@digitalmeld.com> Signed-off-by: Brad Groux <brad@digitalmeld.com>
1 parent cff8957 commit 28b0823

9 files changed

Lines changed: 259 additions & 50 deletions

File tree

crates/buzz-acp/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ All configuration is via environment variables (or CLI flags — every env var h
111111
| `BUZZ_ACP_AGENT_COMMAND` | no | `goose` | Agent binary to spawn. |
112112
| `BUZZ_ACP_AGENT_ARGS` | no | `acp` | Agent arguments (comma-separated). |
113113
| `BUZZ_ACP_MCP_COMMAND` | no | `""` (empty) | Path to an optional MCP server binary to provide to the agent subprocess. |
114+
| `BUZZ_ACP_EXTRA_MCP_COMMANDS` | no | — | Newline-separated additional MCP server commands. Each entry is shell-split with POSIX quoting (e.g. `npx -y my-mcp-server`). An optional `name=` prefix sets the server name explicitly (e.g. `memory=npx -y memory-mcp`), so reordering entries does not silently rename a server and strip the agent of its tools. Without a prefix, names are derived from the executable stem, sanitized to ASCII alphanumeric/hyphen, and disambiguated with numeric suffixes. Extra servers do **not** receive `BUZZ_PRIVATE_KEY`, `BUZZ_RELAY_URL`, or `BUZZ_AUTH_TAG` on the `buzz-agent` MCP spawn path — they are third-party tools, not Buzz-native MCP. **Note:** the credential isolation applies to MCP servers spawned directly by `buzz-agent`'s `McpRegistry`. When using a third-party ACP adapter (e.g. `claude-agent-acp`, `codex-acp`) that spawns its own MCP children, the adapter process inherits the full parent environment including `BUZZ_PRIVATE_KEY`; operators should assume those children can access Buzz credentials unless the adapter itself isolates them. Malformed quoting fails startup with the entry index (the raw command is not logged). |
114115
| `BUZZ_ACP_IDLE_TIMEOUT` | no | `620` | Idle timeout: max seconds of silence before cancelling a turn. Resets on any agent stdout activity. |
115116
| `BUZZ_ACP_MAX_TURN_DURATION` | no | `7200` | Absolute wall-clock cap per turn (safety valve). |
116117
| `BUZZ_API_TOKEN` | no || API token (required if relay enforces token auth). |

crates/buzz-acp/src/acp.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,23 @@ const MAX_LINE_SIZE: usize = 10_000_000; // 10 MB
2626
///
2727
/// Corresponds to the `McpServerStdio` variant in the ACP schema.
2828
/// All four fields are **required** by the schema (`args` and `env` may be empty arrays).
29+
/// `trusted` controls whether the agent runtime passes Buzz identity credentials
30+
/// (`BUZZ_PRIVATE_KEY`, `BUZZ_RELAY_URL`, `BUZZ_AUTH_TAG`) into the child process.
31+
/// Only the built-in `buzz-dev-mcp` server is trusted; extra MCP servers
32+
/// configured via `BUZZ_ACP_EXTRA_MCP_COMMANDS` are untrusted and receive no
33+
/// Buzz credentials.
2934
#[derive(Debug, Clone, serde::Serialize)]
3035
pub struct McpServer {
3136
pub name: String,
3237
pub command: String,
3338
pub args: Vec<String>,
3439
pub env: Vec<EnvVar>,
40+
#[serde(default, skip_serializing_if = "is_false")]
41+
pub trusted: bool,
42+
}
43+
44+
fn is_false(b: &bool) -> bool {
45+
!b
3546
}
3647

3748
/// A single environment variable for an MCP server.
@@ -2538,6 +2549,7 @@ mod tests {
25382549
value: "nsec1abc".into(),
25392550
},
25402551
],
2552+
trusted: true,
25412553
};
25422554
let serialized = serde_json::to_value(&server).unwrap();
25432555
assert_eq!(serialized["name"].as_str(), Some("test-mcp"));

crates/buzz-acp/src/config.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -268,14 +268,18 @@ pub struct CliArgs {
268268
pub mcp_command: String,
269269

270270
/// Additional MCP server commands to pass to the agent session alongside
271-
/// the primary MCP server. Entries are comma-separated; each entry is
271+
/// the primary MCP server. Entries are newline-separated; each entry is
272272
/// shell-split (shlex) into a command and its args, so quoted paths and
273-
/// arguments with spaces are preserved. Server names are derived from the
274-
/// executable stem and disambiguated with a numeric suffix if duplicates
275-
/// occur (e.g. two `npx` wrappers become `npx` and `npx-2`). Entries with
276-
/// malformed quoting are skipped with a warning. Example:
277-
/// `npx -y mcp-remote https://mcp.tavily.com/mcp/?tavilyApiKey=...,other-server`
278-
#[arg(long, env = "BUZZ_ACP_EXTRA_MCP_COMMANDS", value_delimiter = ',')]
273+
/// arguments with spaces are preserved. An optional `name=` prefix sets
274+
/// the server name explicitly (e.g. `memory=npx -y memory-mcp`); without
275+
/// it, the name is derived from the executable stem. Duplicate names are
276+
/// disambiguated with a numeric suffix (e.g. two `npx` wrappers become
277+
/// `npx` and `npx-2`), but explicit names are preferred so reordering
278+
/// entries does not silently rename a server. Entries with malformed
279+
/// quoting fail startup with the entry index (the raw command is not
280+
/// echoed). Example:
281+
/// `memory=npx -y mcp-remote https://mcp.tavily.com/mcp/?tavilyApiKey=...\nother-server --port 8080`
282+
#[arg(long, env = "BUZZ_ACP_EXTRA_MCP_COMMANDS", value_delimiter = '\n')]
279283
pub extra_mcp_commands: Vec<String>,
280284

281285
/// Idle timeout: max seconds of silence before killing a turn.

0 commit comments

Comments
 (0)