mirror of
https://github.com/openai/codex.git
synced 2026-05-05 22:01:37 +03:00
feat(core): add structured network approval plumbing and policy decision model (#11672)
### Description #### Summary Introduces the core plumbing required for structured network approvals #### What changed - Added structured network policy decision modeling in core. - Added approval payload/context types needed for network approval semantics. - Wired shell/unified-exec runtime plumbing to consume structured decisions. - Updated related core error/event surfaces for structured handling. - Updated protocol plumbing used by core approval flow. - Included small CLI debug sandbox compatibility updates needed by this layer. #### Why establishes the minimal backend foundation for network approvals without yet changing high-level orchestration or TUI behavior. #### Notes - Behavior remains constrained by existing requirements/config gating. - Follow-up PRs in the stack handle orchestration, UX, and app-server integration. --------- Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,7 @@ use tokio::io::AsyncReadExt;
|
||||
use tokio::io::BufReader;
|
||||
use tokio::process::Child;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result;
|
||||
@@ -66,6 +67,7 @@ pub struct ExecParams {
|
||||
pub expiration: ExecExpiration,
|
||||
pub env: HashMap<String, String>,
|
||||
pub network: Option<NetworkProxy>,
|
||||
pub network_attempt_id: Option<Uuid>,
|
||||
pub sandbox_permissions: SandboxPermissions,
|
||||
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
|
||||
pub justification: Option<String>,
|
||||
@@ -184,13 +186,15 @@ pub async fn process_exec_tool_call(
|
||||
mut env,
|
||||
expiration,
|
||||
network,
|
||||
network_attempt_id,
|
||||
sandbox_permissions,
|
||||
windows_sandbox_level,
|
||||
justification,
|
||||
arg0: _,
|
||||
} = params;
|
||||
let network_attempt_id = network_attempt_id.map(|attempt_id| attempt_id.to_string());
|
||||
if let Some(network) = network.as_ref() {
|
||||
network.apply_to_env(&mut env);
|
||||
network.apply_to_env_for_attempt(&mut env, network_attempt_id.as_deref());
|
||||
}
|
||||
let (program, args) = command.split_first().ok_or_else(|| {
|
||||
CodexErr::Io(io::Error::new(
|
||||
@@ -238,6 +242,7 @@ pub(crate) async fn execute_exec_env(
|
||||
cwd,
|
||||
env,
|
||||
network,
|
||||
network_attempt_id,
|
||||
expiration,
|
||||
sandbox,
|
||||
windows_sandbox_level,
|
||||
@@ -246,12 +251,18 @@ pub(crate) async fn execute_exec_env(
|
||||
arg0,
|
||||
} = env;
|
||||
|
||||
let network_attempt_id = match network_attempt_id.as_deref() {
|
||||
Some(attempt_id) => Uuid::parse_str(attempt_id).ok(),
|
||||
None => network.as_ref().map(|_| Uuid::new_v4()),
|
||||
};
|
||||
|
||||
let params = ExecParams {
|
||||
command,
|
||||
cwd,
|
||||
expiration,
|
||||
env,
|
||||
network,
|
||||
network: network.clone(),
|
||||
network_attempt_id,
|
||||
sandbox_permissions,
|
||||
windows_sandbox_level,
|
||||
justification,
|
||||
@@ -345,12 +356,14 @@ async fn exec_windows_sandbox(
|
||||
cwd,
|
||||
mut env,
|
||||
network,
|
||||
network_attempt_id,
|
||||
expiration,
|
||||
windows_sandbox_level,
|
||||
..
|
||||
} = params;
|
||||
let network_attempt_id = network_attempt_id.map(|attempt_id| attempt_id.to_string());
|
||||
if let Some(network) = network.as_ref() {
|
||||
network.apply_to_env(&mut env);
|
||||
network.apply_to_env_for_attempt(&mut env, network_attempt_id.as_deref());
|
||||
}
|
||||
|
||||
// TODO(iceweasel-oai): run_windows_sandbox_capture should support all
|
||||
@@ -490,6 +503,7 @@ fn finalize_exec_result(
|
||||
if is_likely_sandbox_denied(sandbox_type, &exec_output) {
|
||||
return Err(CodexErr::Sandbox(SandboxErr::Denied {
|
||||
output: Box::new(exec_output),
|
||||
network_policy_decision: None,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -701,13 +715,19 @@ async fn exec(
|
||||
let ExecParams {
|
||||
command,
|
||||
cwd,
|
||||
env,
|
||||
mut env,
|
||||
network,
|
||||
network_attempt_id,
|
||||
arg0,
|
||||
expiration,
|
||||
windows_sandbox_level: _,
|
||||
..
|
||||
} = params;
|
||||
let network_attempt_id = network_attempt_id.map(|attempt_id| attempt_id.to_string());
|
||||
|
||||
if let Some(network) = network.as_ref() {
|
||||
network.apply_to_env_for_attempt(&mut env, network_attempt_id.as_deref());
|
||||
}
|
||||
|
||||
let (program, args) = command.split_first().ok_or_else(|| {
|
||||
CodexErr::Io(io::Error::new(
|
||||
@@ -722,7 +742,10 @@ async fn exec(
|
||||
arg0: arg0_ref,
|
||||
cwd,
|
||||
sandbox_policy,
|
||||
network: network.as_ref(),
|
||||
// The environment already has attempt-scoped proxy settings from
|
||||
// apply_to_env_for_attempt above. Passing network here would reapply
|
||||
// non-attempt proxy vars and drop attempt correlation metadata.
|
||||
network: None,
|
||||
stdio_policy: StdioPolicy::RedirectForShellTool,
|
||||
env,
|
||||
})
|
||||
@@ -951,6 +974,17 @@ mod tests {
|
||||
assert!(!is_likely_sandbox_denied(SandboxType::None, &output));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_ignores_network_policy_text_in_non_sandbox_mode() {
|
||||
let output = make_exec_output(
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","reason":"not_allowed","source":"decider","protocol":"http","host":"google.com","port":80}"#,
|
||||
);
|
||||
assert!(!is_likely_sandbox_denied(SandboxType::None, &output));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_uses_aggregated_output() {
|
||||
let output = make_exec_output(
|
||||
@@ -965,6 +999,21 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_detection_ignores_network_policy_text_with_zero_exit_code() {
|
||||
let output = make_exec_output(
|
||||
0,
|
||||
"",
|
||||
"",
|
||||
r#"CODEX_NETWORK_POLICY_DECISION {"decision":"ask","source":"decider","protocol":"http","host":"google.com","port":80}"#,
|
||||
);
|
||||
|
||||
assert!(!is_likely_sandbox_denied(
|
||||
SandboxType::LinuxSeccomp,
|
||||
&output
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_capped_limits_retained_bytes() {
|
||||
let (mut writer, reader) = tokio::io::duplex(1024);
|
||||
@@ -1088,6 +1137,7 @@ mod tests {
|
||||
expiration: 500.into(),
|
||||
env,
|
||||
network: None,
|
||||
network_attempt_id: None,
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
|
||||
justification: None,
|
||||
@@ -1141,6 +1191,7 @@ mod tests {
|
||||
expiration: ExecExpiration::Cancellation(cancel_token),
|
||||
env,
|
||||
network: None,
|
||||
network_attempt_id: None,
|
||||
sandbox_permissions: SandboxPermissions::UseDefault,
|
||||
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
|
||||
justification: None,
|
||||
|
||||
Reference in New Issue
Block a user