Skip to content

Commit 775f676

Browse files
authored
feat(security): MCP/A2A security hardening — tool collision detection, SMCP lifecycle, IBCT tokens (#2533)
* feat(security): MCP/A2A security hardening — tool collision detection, list locking, env isolation, intent-anchor wrapper, IBCT (#2496, #2497, #2504) Phase 1 (no new deps): - Cross-server sanitized_id collision detection in McpManager: warnings on connect and add_server, first-registered tool wins dispatch (MF-1, SF-6) - Tool-list snapshot locking (lock_tool_list config): tools/list_changed rejected for connected servers; lock set atomically before connect_entry to eliminate TOCTOU race (MF-2) - Per-server Stdio environment isolation (env_isolation config): spawned processes receive only BASE_ENV_VARS + server env; XDG dirs included for Linux (SF-3) - Intent-anchor wrapper for MCP tool output: per-invocation UUID nonce boundary prevents delimiter injection; [TOOL_OUTPUT:: escaped in content (MF-5) Phase 2 (hmac 0.13 + sha2 0.11): - IBCT module (crates/zeph-a2a/src/ibct.rs): HMAC-SHA256, key_id field for rotation, Vec<IbctKey> for graceful key rollover, base64-JSON X-Zeph-IBCT header, vault_ref config field for secure key storage (MF-3, MF-4) - ibct feature gate in zeph-a2a; enabled via a2a workspace feature * fix(security): address code review issues RC-1, RC-2, RC-3, REC-1 RC-1: replace hex string comparison in ibct.rs with constant-time verify_signature() using Mac::verify_slice() to eliminate the timing side-channel in HMAC verification. RC-2: remove hardcoded trust=untrusted field from intent-anchor wrapper format; the trust annotation was redundant and potentially misleading since callers already control context. RC-3: replace all .expect("connected_server_ids lock poisoned") with .unwrap_or_else(PoisonError::into_inner) to avoid cascade panics on RwLock poison in manager.rs. REC-1: add tool_list_locked.remove() in add_server() error branches for list_tools and run_probe failures, ensuring the lock is always cleaned up on early return. * test(security): add unit tests for build_isolated_env and detect_collisions
1 parent 5c008e3 commit 775f676

File tree

20 files changed

+1096
-20
lines changed

20 files changed

+1096
-20
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4949
- new `set_working_directory` tool — explicit, persistent cwd change detected post-tool and fires `CwdChanged` hooks with `ZEPH_OLD_CWD`/`ZEPH_NEW_CWD` env vars
5050
- `FileChangeWatcher` monitors configured `watch_paths` via `notify-debouncer-mini` (debounced 500ms by default) and fires hooks with `ZEPH_CHANGED_PATH`
5151
- TUI spinner status emitted on both event types
52+
- security(mcp): cross-server `sanitized_id` collision detection in `McpManager` — logged as warnings on connect and `add_server`; first-registered tool wins dispatch (#2496)
53+
- security(mcp): tool-list snapshot locking (`[mcp] lock_tool_list = true`) — `tools/list_changed` refresh events are rejected for connected servers, preventing mid-session tool injection; lock flag set atomically before connection to eliminate TOCTOU race (#2497)
54+
- security(mcp): per-server Stdio environment isolation (`env_isolation = true` per server or `[mcp] default_env_isolation = true`) — spawned processes only receive a minimal base env plus server-specific `env` map; XDG dirs included for Linux compatibility (#2497)
55+
- security(mcp): intent-anchor wrapper for MCP tool output — each result is wrapped with a per-invocation nonce boundary `[TOOL_OUTPUT::{uuid}::BEGIN/END]`; boundary injection prevented by escaping `[TOOL_OUTPUT::` in tool output (#2497)
56+
- security(a2a): Invocation-Bound Capability Tokens (IBCT) — `crates/zeph-a2a/src/ibct.rs`; HMAC-SHA256 signing, `key_id` field for key rotation, base64-JSON `X-Zeph-IBCT` header; gated on `ibct` feature (#2504)
57+
- config: `[mcp] lock_tool_list`, `[mcp] default_env_isolation`, `[[mcp.servers]] env_isolation` fields
58+
- config: `[a2a] ibct_keys`, `[a2a] ibct_signing_key_vault_ref`, `[a2a] ibct_ttl_secs` fields
5259

5360
### Removed
5461

Cargo.lock

Lines changed: 35 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ rmcp = "1.3"
7171
audioadapter-buffers = "2.0"
7272
rubato = "1.0"
7373
schemars = "1.2"
74+
hmac = "0.13"
7475
sha2 = "0.11"
7576
scrape-core = "0.2"
7677
semver = "1.0.27"
@@ -176,7 +177,7 @@ full = ["desktop", "ide", "server", "chat", "pdf", "stt", "acp-unstable", "ex
176177
bundled-skills = ["zeph-skills/bundled-skills", "zeph-core/bundled-skills"]
177178

178179
# === Individual feature flags ===
179-
a2a = ["dep:zeph-a2a", "zeph-a2a?/server"]
180+
a2a = ["dep:zeph-a2a", "zeph-a2a?/server", "zeph-a2a?/ibct"]
180181
compression-guidelines = ["zeph-core/compression-guidelines", "zeph-memory/compression-guidelines"]
181182
acp = ["dep:zeph-acp"]
182183
acp-http = ["acp", "dep:axum", "zeph-acp/acp-http"]

crates/zeph-a2a/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@ description = "A2A protocol client and server with agent discovery for Zeph"
1414
readme = "README.md"
1515

1616
[features]
17+
ibct = ["dep:hmac", "dep:sha2"]
1718
server = ["dep:axum", "dep:blake3", "dep:subtle", "dep:tower", "dep:tower-http"]
1819

1920
[dependencies]
2021
axum = { workspace = true, optional = true }
22+
base64.workspace = true
2123
blake3 = { workspace = true, optional = true }
2224
eventsource-stream.workspace = true
25+
hex.workspace = true
26+
hmac = { workspace = true, optional = true }
27+
sha2 = { workspace = true, optional = true }
2328
futures.workspace = true
2429
futures-core.workspace = true
2530
reqwest = { workspace = true, features = ["json", "stream"] }

0 commit comments

Comments
 (0)