mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(buzz-acp): inject Codex network allowlist for relay hostname at spawn time (#1287)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
co-authored by
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7
parent
dd5592e34f
commit
0379247eba
@@ -9,6 +9,7 @@ use std::path::PathBuf;
|
||||
use clap::Parser;
|
||||
use nostr::Keys;
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::filter::SubscriptionRule;
|
||||
@@ -517,6 +518,57 @@ fn default_agent_args(command: &str) -> Option<Vec<String>> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build `-c` flag pairs that allowlist the relay hostname in Codex's network sandbox.
|
||||
///
|
||||
/// Codex sandboxes MCP subprocesses (including `buzz-cli`) behind a local proxy with
|
||||
/// a domain allowlist. Without this, `buzz-cli` requests to the relay are blocked before
|
||||
/// they reach WARP or any other outbound network path.
|
||||
///
|
||||
/// Returns `["-c", "network_proxy.mode=\"full\"", "-c", "network_proxy.domains.\"<host>\"=\"allow\""]`
|
||||
/// for Codex agents, or an empty vec for non-Codex agents or when the hostname cannot
|
||||
/// be parsed from the relay URL.
|
||||
///
|
||||
/// The `network_proxy` keys map to `NetworkProxyConfigToml` in codex-acp's config schema:
|
||||
/// - `network_proxy.mode="full"` enables the managed proxy for all outbound traffic
|
||||
/// - `network_proxy.domains."<host>"="allow"` adds the relay hostname to the allowlist
|
||||
///
|
||||
/// Handles `ws://`, `wss://`, `http://`, and `https://` schemes. Port is stripped —
|
||||
/// Codex's domain allowlist matches on hostname only.
|
||||
pub fn codex_network_args(agent_command: &str, relay_url: &str) -> Vec<String> {
|
||||
match normalize_agent_command_identity(agent_command).as_str() {
|
||||
"codex" | "codex-acp" => {}
|
||||
_ => return vec![],
|
||||
}
|
||||
|
||||
// Use the `url` crate so ws://, wss://, http://, https:// are all handled
|
||||
// correctly. On parse failure, skip injection rather than panicking.
|
||||
let host = match Url::parse(relay_url) {
|
||||
Ok(u) => match u.host_str() {
|
||||
Some(h) => h.to_owned(),
|
||||
None => {
|
||||
tracing::warn!(
|
||||
relay_url,
|
||||
"codex network allowlist: no host in relay URL — skipping injection"
|
||||
);
|
||||
return vec![];
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::warn!(relay_url, error = %e, "codex network allowlist: failed to parse relay URL — skipping injection");
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
|
||||
tracing::debug!(host, "injecting codex network allowlist for host");
|
||||
|
||||
vec![
|
||||
"-c".into(),
|
||||
"network_proxy.mode=\"full\"".into(),
|
||||
"-c".into(),
|
||||
format!("network_proxy.domains.\"{host}\"=\"allow\""),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn normalize_agent_args(command: &str, agent_args: Vec<String>) -> Vec<String> {
|
||||
let normalized = agent_args
|
||||
.into_iter()
|
||||
@@ -645,7 +697,16 @@ impl Config {
|
||||
));
|
||||
}
|
||||
|
||||
let agent_args = normalize_agent_args(&agent_command, args.agent_args);
|
||||
let mut agent_args = normalize_agent_args(&agent_command, args.agent_args);
|
||||
|
||||
// Prepend Codex network allowlist flags so buzz-cli (an MCP subprocess)
|
||||
// can reach the relay through Codex's sandbox proxy. No-op for non-Codex agents.
|
||||
let network_args = codex_network_args(&agent_command, &args.relay_url);
|
||||
if !network_args.is_empty() {
|
||||
let mut merged = network_args;
|
||||
merged.extend(agent_args);
|
||||
agent_args = merged;
|
||||
}
|
||||
|
||||
// Finding #49b — warn on invalid UUIDs in --channels.
|
||||
if let Some(ref channels) = args.channels {
|
||||
@@ -1361,6 +1422,113 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// --- codex_network_args tests ---
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_wss_url() {
|
||||
let args = codex_network_args("codex-acp", "wss://sprout-oss.stage.blox.sqprod.co");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"-c",
|
||||
"network_proxy.mode=\"full\"",
|
||||
"-c",
|
||||
"network_proxy.domains.\"sprout-oss.stage.blox.sqprod.co\"=\"allow\"",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_ws_url() {
|
||||
let args = codex_network_args("codex-acp", "ws://localhost:3000");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"-c",
|
||||
"network_proxy.mode=\"full\"",
|
||||
"-c",
|
||||
"network_proxy.domains.\"localhost\"=\"allow\"",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_https_url() {
|
||||
let args = codex_network_args("codex-acp", "https://relay.example.com/path");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"-c",
|
||||
"network_proxy.mode=\"full\"",
|
||||
"-c",
|
||||
"network_proxy.domains.\"relay.example.com\"=\"allow\"",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_http_url_with_port() {
|
||||
let args = codex_network_args("codex-acp", "http://relay.example.com:8080/query");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"-c",
|
||||
"network_proxy.mode=\"full\"",
|
||||
"-c",
|
||||
"network_proxy.domains.\"relay.example.com\"=\"allow\"",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_bare_codex_command() {
|
||||
// "codex" (not "codex-acp") should also get the args.
|
||||
let args = codex_network_args("codex", "wss://relay.example.com");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"-c",
|
||||
"network_proxy.mode=\"full\"",
|
||||
"-c",
|
||||
"network_proxy.domains.\"relay.example.com\"=\"allow\"",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_full_path_codex_command() {
|
||||
// Full path like /usr/local/bin/codex-acp should be normalized.
|
||||
let args = codex_network_args("/usr/local/bin/codex-acp", "wss://relay.example.com");
|
||||
assert_eq!(
|
||||
args,
|
||||
vec![
|
||||
"-c",
|
||||
"network_proxy.mode=\"full\"",
|
||||
"-c",
|
||||
"network_proxy.domains.\"relay.example.com\"=\"allow\"",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_non_codex_agent_returns_empty() {
|
||||
assert!(codex_network_args("goose", "wss://relay.example.com").is_empty());
|
||||
assert!(codex_network_args("claude-agent-acp", "wss://relay.example.com").is_empty());
|
||||
assert!(codex_network_args("buzz-agent", "wss://relay.example.com").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_empty_relay_url_returns_empty() {
|
||||
// Empty string fails Url::parse — graceful empty return.
|
||||
assert!(codex_network_args("codex-acp", "").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_network_args_schemeless_string_returns_empty() {
|
||||
// A bare string with no scheme fails Url::parse — graceful empty return.
|
||||
assert!(codex_network_args("codex-acp", "not-a-url").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_mode_wildcard() {
|
||||
let config = test_config(SubscribeMode::All);
|
||||
|
||||
Reference in New Issue
Block a user