From fa8f0f69965a49ea0f7ced7b518733711c622784 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Thu, 16 Apr 2026 13:25:50 -0700 Subject: [PATCH 1/6] feat: add guardian network approval trigger context Co-authored-by: Codex noreply@openai.com --- .../core/src/guardian/approval_request.rs | 50 +++++++++++--- codex-rs/core/src/guardian/mod.rs | 1 + codex-rs/core/src/guardian/tests.rs | 44 +++++++++++++ codex-rs/core/src/tools/network_approval.rs | 35 +++++++--- .../core/src/tools/network_approval_tests.rs | 66 +++++++++++++++---- codex-rs/core/src/tools/runtimes/shell.rs | 13 +++- .../core/src/tools/runtimes/unified_exec.rs | 13 +++- 7 files changed, 193 insertions(+), 29 deletions(-) diff --git a/codex-rs/core/src/guardian/approval_request.rs b/codex-rs/core/src/guardian/approval_request.rs index 6d1d3f76afb8..83972ae38ad9 100644 --- a/codex-rs/core/src/guardian/approval_request.rs +++ b/codex-rs/core/src/guardian/approval_request.rs @@ -52,6 +52,7 @@ pub(crate) enum GuardianApprovalRequest { host: String, protocol: NetworkApprovalProtocol, port: u16, + trigger: Option, }, McpToolCall { id: String, @@ -67,6 +68,22 @@ pub(crate) enum GuardianApprovalRequest { }, } +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct GuardianNetworkAccessTrigger { + pub(crate) call_id: String, + pub(crate) tool_name: String, + pub(crate) command: Vec, + pub(crate) cwd: AbsolutePathBuf, + pub(crate) sandbox_permissions: crate::sandboxing::SandboxPermissions, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) additional_permissions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) justification: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) tty: Option, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub(crate) struct GuardianMcpAnnotations { #[serde(skip_serializing_if = "Option::is_none")] @@ -123,6 +140,18 @@ struct McpToolCallApprovalAction<'a> { annotations: Option<&'a GuardianMcpAnnotations>, } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct NetworkAccessApprovalAction<'a> { + tool: &'static str, + target: &'a str, + host: &'a str, + protocol: NetworkApprovalProtocol, + port: u16, + #[serde(skip_serializing_if = "Option::is_none")] + trigger: Option<&'a GuardianNetworkAccessTrigger>, +} + fn serialize_guardian_action(value: impl Serialize) -> serde_json::Result { serde_json::to_value(value) } @@ -263,13 +292,15 @@ pub(crate) fn guardian_approval_request_to_json( host, protocol, port, - } => Ok(serde_json::json!({ - "tool": "network_access", - "target": target, - "host": host, - "protocol": protocol, - "port": port, - })), + trigger, + } => serialize_guardian_action(NetworkAccessApprovalAction { + tool: "network_access", + target, + host, + protocol: *protocol, + port: *port, + trigger: trigger.as_ref(), + }), GuardianApprovalRequest::McpToolCall { id: _, server, @@ -332,6 +363,7 @@ pub(crate) fn guardian_assessment_action( host, protocol, port, + trigger: _, } => GuardianAssessmentAction::NetworkAccess { target: target.clone(), host: host.clone(), @@ -361,7 +393,9 @@ pub(crate) fn guardian_request_target_item_id(request: &GuardianApprovalRequest) | GuardianApprovalRequest::ExecCommand { id, .. } | GuardianApprovalRequest::ApplyPatch { id, .. } | GuardianApprovalRequest::McpToolCall { id, .. } => Some(id), - GuardianApprovalRequest::NetworkAccess { .. } => None, + GuardianApprovalRequest::NetworkAccess { trigger, .. } => { + trigger.as_ref().map(|trigger| trigger.call_id.as_str()) + } #[cfg(unix)] GuardianApprovalRequest::Execve { id, .. } => Some(id), } diff --git a/codex-rs/core/src/guardian/mod.rs b/codex-rs/core/src/guardian/mod.rs index 67e9a828ee1a..b2c8ef71f705 100644 --- a/codex-rs/core/src/guardian/mod.rs +++ b/codex-rs/core/src/guardian/mod.rs @@ -24,6 +24,7 @@ use serde::Serialize; pub(crate) use approval_request::GuardianApprovalRequest; pub(crate) use approval_request::GuardianMcpAnnotations; +pub(crate) use approval_request::GuardianNetworkAccessTrigger; pub(crate) use approval_request::guardian_approval_request_to_json; pub(crate) use review::guardian_rejection_message; pub(crate) use review::guardian_timeout_message; diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 485f22255199..05964fed67a8 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -633,6 +633,49 @@ fn guardian_approval_request_to_json_renders_mcp_tool_call_shape() -> serde_json Ok(()) } +#[test] +fn guardian_approval_request_to_json_renders_network_access_trigger() -> serde_json::Result<()> { + let action = GuardianApprovalRequest::NetworkAccess { + id: "network-1".to_string(), + turn_id: "turn-1".to_string(), + target: "https://example.com:443".to_string(), + host: "example.com".to_string(), + protocol: NetworkApprovalProtocol::Https, + port: 443, + trigger: Some(GuardianNetworkAccessTrigger { + call_id: "call-1".to_string(), + tool_name: "shell".to_string(), + command: vec!["curl".to_string(), "https://example.com".to_string()], + cwd: test_path_buf("/repo").abs(), + sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault, + additional_permissions: None, + justification: Some("Fetch the release metadata.".to_string()), + tty: None, + }), + }; + + assert_eq!( + guardian_approval_request_to_json(&action)?, + serde_json::json!({ + "tool": "network_access", + "target": "https://example.com:443", + "host": "example.com", + "protocol": "https", + "port": 443, + "trigger": { + "callId": "call-1", + "toolName": "shell", + "command": ["curl", "https://example.com"], + "cwd": "/repo", + "sandboxPermissions": "use_default", + "justification": "Fetch the release metadata.", + }, + }) + ); + + Ok(()) +} + #[test] fn guardian_assessment_action_redacts_apply_patch_patch_text() { let cwd = test_path_buf("/tmp").abs(); @@ -664,6 +707,7 @@ fn guardian_request_turn_id_prefers_network_access_owner_turn() { host: "example.com".to_string(), protocol: NetworkApprovalProtocol::Https, port: 443, + trigger: None, }; let apply_patch = GuardianApprovalRequest::ApplyPatch { id: "patch-1".to_string(), diff --git a/codex-rs/core/src/tools/network_approval.rs b/codex-rs/core/src/tools/network_approval.rs index de689ea25d1d..c7da5b191fef 100644 --- a/codex-rs/core/src/tools/network_approval.rs +++ b/codex-rs/core/src/tools/network_approval.rs @@ -1,5 +1,6 @@ use crate::codex::Session; use crate::guardian::GuardianApprovalRequest; +use crate::guardian::GuardianNetworkAccessTrigger; use crate::guardian::guardian_rejection_message; use crate::guardian::guardian_timeout_message; use crate::guardian::new_guardian_review_id; @@ -43,6 +44,7 @@ pub(crate) enum NetworkApprovalMode { pub(crate) struct NetworkApprovalSpec { pub network: Option, pub mode: NetworkApprovalMode, + pub trigger: GuardianNetworkAccessTrigger, } #[derive(Clone, Debug)] @@ -172,6 +174,7 @@ impl PendingHostApproval { struct ActiveNetworkApprovalCall { registration_id: String, turn_id: String, + trigger: GuardianNetworkAccessTrigger, } pub(crate) struct NetworkApprovalService { @@ -204,7 +207,12 @@ impl NetworkApprovalService { other_approved_hosts.extend(approved_hosts.iter().cloned()); } - async fn register_call(&self, registration_id: String, turn_id: String) { + async fn register_call( + &self, + registration_id: String, + turn_id: String, + trigger: GuardianNetworkAccessTrigger, + ) { let mut active_calls = self.active_calls.lock().await; let key = registration_id.clone(); active_calls.insert( @@ -212,6 +220,7 @@ impl NetworkApprovalService { Arc::new(ActiveNetworkApprovalCall { registration_id, turn_id, + trigger, }), ); } @@ -365,13 +374,18 @@ impl NetworkApprovalService { return NetworkDecision::deny(REASON_NOT_ALLOWED); } + let guardian_approval_id = Self::approval_id_for_key(&key); + let use_guardian = routes_approval_to_guardian(&turn_context); + let owner_call = self.resolve_single_active_call().await; + let trigger = if use_guardian { + owner_call.as_ref().map(|call| call.trigger.clone()) + } else { + None + }; let network_approval_context = NetworkApprovalContext { host: request.host.clone(), protocol, }; - let owner_call = self.resolve_single_active_call().await; - let guardian_approval_id = Self::approval_id_for_key(&key); - let use_guardian = routes_approval_to_guardian(&turn_context); let guardian_review_id = use_guardian.then(new_guardian_review_id); let approval_decision = if let Some(review_id) = guardian_review_id.clone() { review_approval_request( @@ -387,6 +401,7 @@ impl NetworkApprovalService { host: request.host, protocol, port: key.port, + trigger, }, Some(policy_denial_message.clone()), ) @@ -581,8 +596,12 @@ pub(crate) async fn begin_network_approval( managed_network_active: bool, spec: Option, ) -> Option { - let spec = spec?; - if !managed_network_active || spec.network.is_none() { + let NetworkApprovalSpec { + network, + mode, + trigger, + } = spec?; + if !managed_network_active || network.is_none() { return None; } @@ -590,12 +609,12 @@ pub(crate) async fn begin_network_approval( session .services .network_approval - .register_call(registration_id.clone(), turn_id.to_string()) + .register_call(registration_id.clone(), turn_id.to_string(), trigger) .await; Some(ActiveNetworkApproval { registration_id: Some(registration_id), - mode: spec.mode, + mode, }) } diff --git a/codex-rs/core/src/tools/network_approval_tests.rs b/codex-rs/core/src/tools/network_approval_tests.rs index ca777e7cd462..8680bf08f61f 100644 --- a/codex-rs/core/src/tools/network_approval_tests.rs +++ b/codex-rs/core/src/tools/network_approval_tests.rs @@ -1,7 +1,10 @@ use super::*; +use crate::sandboxing::SandboxPermissions; use codex_network_proxy::BlockedRequestArgs; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::SandboxPolicy; +use core_test_support::PathBufExt; +use core_test_support::test_path_buf; use pretty_assertions::assert_eq; #[tokio::test] @@ -207,13 +210,60 @@ fn denied_blocked_request(host: &str) -> BlockedRequest { }) } +async fn register_test_call(service: &NetworkApprovalService, registration_id: &str) { + service + .register_call( + registration_id.to_string(), + "turn-1".to_string(), + GuardianNetworkAccessTrigger { + call_id: "call-1".to_string(), + tool_name: "shell".to_string(), + command: vec!["curl".to_string(), "https://example.com".to_string()], + cwd: test_path_buf("/tmp").abs(), + sandbox_permissions: SandboxPermissions::UseDefault, + additional_permissions: None, + justification: None, + tty: None, + }, + ) + .await; +} + #[tokio::test] -async fn record_blocked_request_sets_policy_outcome_for_owner_call() { +async fn active_call_preserves_triggering_command_context() { let service = NetworkApprovalService::default(); + let expected = GuardianNetworkAccessTrigger { + call_id: "call-1".to_string(), + tool_name: "shell".to_string(), + command: vec!["curl".to_string(), "https://example.com".to_string()], + cwd: test_path_buf("/repo").abs(), + sandbox_permissions: SandboxPermissions::UseDefault, + additional_permissions: None, + justification: Some("fetch release metadata".to_string()), + tty: None, + }; + service - .register_call("registration-1".to_string(), "turn-1".to_string()) + .register_call( + "registration-1".to_string(), + "turn-1".to_string(), + expected.clone(), + ) .await; + let call = service + .resolve_single_active_call() + .await + .expect("single active call should resolve"); + + assert_eq!(&call.trigger, &expected); +} + +#[tokio::test] +async fn record_blocked_request_sets_policy_outcome_for_owner_call() { + let service = NetworkApprovalService::default(); + register_test_call(&service, "registration-1").await; + service .record_blocked_request(denied_blocked_request("example.com")) .await; @@ -229,9 +279,7 @@ async fn record_blocked_request_sets_policy_outcome_for_owner_call() { #[tokio::test] async fn blocked_request_policy_does_not_override_user_denial_outcome() { let service = NetworkApprovalService::default(); - service - .register_call("registration-1".to_string(), "turn-1".to_string()) - .await; + register_test_call(&service, "registration-1").await; service .record_call_outcome("registration-1", NetworkApprovalOutcome::DeniedByUser) @@ -249,12 +297,8 @@ async fn blocked_request_policy_does_not_override_user_denial_outcome() { #[tokio::test] async fn record_blocked_request_ignores_ambiguous_unattributed_blocked_requests() { let service = NetworkApprovalService::default(); - service - .register_call("registration-1".to_string(), "turn-1".to_string()) - .await; - service - .register_call("registration-2".to_string(), "turn-1".to_string()) - .await; + register_test_call(&service, "registration-1").await; + register_test_call(&service, "registration-2").await; service .record_blocked_request(denied_blocked_request("example.com")) diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index 203553bf874d..c66c08351ea1 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -11,6 +11,7 @@ pub(crate) mod zsh_fork_backend; use crate::command_canonicalization::canonicalize_command_for_approval; use crate::exec::ExecCapturePolicy; use crate::guardian::GuardianApprovalRequest; +use crate::guardian::GuardianNetworkAccessTrigger; use crate::guardian::review_approval_request; use crate::sandboxing::ExecOptions; use crate::sandboxing::SandboxPermissions; @@ -206,12 +207,22 @@ impl ToolRuntime for ShellRuntime { fn network_approval_spec( &self, req: &ShellRequest, - _ctx: &ToolCtx, + ctx: &ToolCtx, ) -> Option { req.network.as_ref()?; Some(NetworkApprovalSpec { network: req.network.clone(), mode: NetworkApprovalMode::Immediate, + trigger: GuardianNetworkAccessTrigger { + call_id: ctx.call_id.clone(), + tool_name: ctx.tool_name.clone(), + command: req.command.clone(), + cwd: req.cwd.clone(), + sandbox_permissions: req.sandbox_permissions, + additional_permissions: req.additional_permissions.clone(), + justification: req.justification.clone(), + tty: None, + }, }) } diff --git a/codex-rs/core/src/tools/runtimes/unified_exec.rs b/codex-rs/core/src/tools/runtimes/unified_exec.rs index b241d4738985..a7b923be1987 100644 --- a/codex-rs/core/src/tools/runtimes/unified_exec.rs +++ b/codex-rs/core/src/tools/runtimes/unified_exec.rs @@ -8,6 +8,7 @@ use crate::command_canonicalization::canonicalize_command_for_approval; use crate::exec::ExecCapturePolicy; use crate::exec::ExecExpiration; use crate::guardian::GuardianApprovalRequest; +use crate::guardian::GuardianNetworkAccessTrigger; use crate::guardian::review_approval_request; use crate::sandboxing::ExecOptions; use crate::sandboxing::ExecServerEnvConfig; @@ -188,12 +189,22 @@ impl<'a> ToolRuntime for UnifiedExecRunt fn network_approval_spec( &self, req: &UnifiedExecRequest, - _ctx: &ToolCtx, + ctx: &ToolCtx, ) -> Option { req.network.as_ref()?; Some(NetworkApprovalSpec { network: req.network.clone(), mode: NetworkApprovalMode::Deferred, + trigger: GuardianNetworkAccessTrigger { + call_id: ctx.call_id.clone(), + tool_name: ctx.tool_name.clone(), + command: req.command.clone(), + cwd: req.cwd.clone(), + sandbox_permissions: req.sandbox_permissions, + additional_permissions: req.additional_permissions.clone(), + justification: req.justification.clone(), + tty: Some(req.tty), + }, }) } From 56954d269f1423dacd7ee115628c9f4735b7c2aa Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Thu, 16 Apr 2026 15:27:47 -0700 Subject: [PATCH 2/6] test: make network approval trigger test path portable Co-authored-by: Codex --- codex-rs/core/src/guardian/tests.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 05964fed67a8..272031615580 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -635,6 +635,7 @@ fn guardian_approval_request_to_json_renders_mcp_tool_call_shape() -> serde_json #[test] fn guardian_approval_request_to_json_renders_network_access_trigger() -> serde_json::Result<()> { + let cwd = test_path_buf("/repo").abs(); let action = GuardianApprovalRequest::NetworkAccess { id: "network-1".to_string(), turn_id: "turn-1".to_string(), @@ -646,7 +647,7 @@ fn guardian_approval_request_to_json_renders_network_access_trigger() -> serde_j call_id: "call-1".to_string(), tool_name: "shell".to_string(), command: vec!["curl".to_string(), "https://example.com".to_string()], - cwd: test_path_buf("/repo").abs(), + cwd: cwd.clone(), sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault, additional_permissions: None, justification: Some("Fetch the release metadata.".to_string()), @@ -666,7 +667,7 @@ fn guardian_approval_request_to_json_renders_network_access_trigger() -> serde_j "callId": "call-1", "toolName": "shell", "command": ["curl", "https://example.com"], - "cwd": "/repo", + "cwd": cwd.to_string_lossy().to_string(), "sandboxPermissions": "use_default", "justification": "Fetch the release metadata.", }, From 08c1def495a3ea0d983415a899d95bee054feddb Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Thu, 16 Apr 2026 15:43:55 -0700 Subject: [PATCH 3/6] fix: preserve network guardian target item scope Co-authored-by: Codex --- .../core/src/guardian/approval_request.rs | 4 +-- codex-rs/core/src/guardian/tests.rs | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/codex-rs/core/src/guardian/approval_request.rs b/codex-rs/core/src/guardian/approval_request.rs index 83972ae38ad9..e93839d01508 100644 --- a/codex-rs/core/src/guardian/approval_request.rs +++ b/codex-rs/core/src/guardian/approval_request.rs @@ -393,9 +393,7 @@ pub(crate) fn guardian_request_target_item_id(request: &GuardianApprovalRequest) | GuardianApprovalRequest::ExecCommand { id, .. } | GuardianApprovalRequest::ApplyPatch { id, .. } | GuardianApprovalRequest::McpToolCall { id, .. } => Some(id), - GuardianApprovalRequest::NetworkAccess { trigger, .. } => { - trigger.as_ref().map(|trigger| trigger.call_id.as_str()) - } + GuardianApprovalRequest::NetworkAccess { .. } => None, #[cfg(unix)] GuardianApprovalRequest::Execve { id, .. } => Some(id), } diff --git a/codex-rs/core/src/guardian/tests.rs b/codex-rs/core/src/guardian/tests.rs index 272031615580..bbc7a4b3062f 100644 --- a/codex-rs/core/src/guardian/tests.rs +++ b/codex-rs/core/src/guardian/tests.rs @@ -14,6 +14,7 @@ use crate::config_loader::NetworkDomainPermissionToml; use crate::config_loader::NetworkDomainPermissionsToml; use crate::config_loader::RequirementSource; use crate::config_loader::Sourced; +use crate::guardian::approval_request::guardian_request_target_item_id; use crate::test_support; use codex_config::config_toml::ConfigToml; use codex_network_proxy::NetworkProxyConfig; @@ -728,6 +729,30 @@ fn guardian_request_turn_id_prefers_network_access_owner_turn() { ); } +#[test] +fn guardian_request_target_item_id_omits_network_access_trigger_call_id() { + let network_access = GuardianApprovalRequest::NetworkAccess { + id: "network-1".to_string(), + turn_id: "owner-turn".to_string(), + target: "https://example.com:443".to_string(), + host: "example.com".to_string(), + protocol: NetworkApprovalProtocol::Https, + port: 443, + trigger: Some(GuardianNetworkAccessTrigger { + call_id: "call-1".to_string(), + tool_name: "shell".to_string(), + command: vec!["curl".to_string(), "https://example.com".to_string()], + cwd: test_path_buf("/repo").abs(), + sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault, + additional_permissions: None, + justification: None, + tty: None, + }), + }; + + assert_eq!(guardian_request_target_item_id(&network_access), None); +} + #[tokio::test] async fn cancelled_guardian_review_emits_terminal_abort_without_warning() { let (session, turn, rx) = crate::codex::make_session_and_context_with_rx().await; From fcbc8f7d03026df8ee9621f6261c236359045ae5 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Mon, 20 Apr 2026 12:51:29 -0700 Subject: [PATCH 4/6] fix: address network approval review comments Co-authored-by: Codex --- codex-rs/core/src/guardian/approval_request.rs | 4 +--- codex-rs/core/src/tools/network_approval_tests.rs | 13 ++++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/codex-rs/core/src/guardian/approval_request.rs b/codex-rs/core/src/guardian/approval_request.rs index e93839d01508..93e458e02abc 100644 --- a/codex-rs/core/src/guardian/approval_request.rs +++ b/codex-rs/core/src/guardian/approval_request.rs @@ -357,13 +357,11 @@ pub(crate) fn guardian_assessment_action( } } GuardianApprovalRequest::NetworkAccess { - id: _, - turn_id: _, target, host, protocol, port, - trigger: _, + .. } => GuardianAssessmentAction::NetworkAccess { target: target.clone(), host: host.clone(), diff --git a/codex-rs/core/src/tools/network_approval_tests.rs b/codex-rs/core/src/tools/network_approval_tests.rs index 27efb9ef64b5..ac0046228be3 100644 --- a/codex-rs/core/src/tools/network_approval_tests.rs +++ b/codex-rs/core/src/tools/network_approval_tests.rs @@ -210,7 +210,10 @@ fn denied_blocked_request(host: &str) -> BlockedRequest { }) } -async fn register_test_call(service: &NetworkApprovalService, registration_id: &str) { +async fn register_call_with_default_shell_trigger( + service: &NetworkApprovalService, + registration_id: &str, +) { service .register_call( registration_id.to_string(), @@ -265,7 +268,7 @@ async fn active_call_preserves_triggering_command_context() { #[tokio::test] async fn record_blocked_request_sets_policy_outcome_for_owner_call() { let service = NetworkApprovalService::default(); - register_test_call(&service, "registration-1").await; + register_call_with_default_shell_trigger(&service, "registration-1").await; service .record_blocked_request(denied_blocked_request("example.com")) @@ -282,7 +285,7 @@ async fn record_blocked_request_sets_policy_outcome_for_owner_call() { #[tokio::test] async fn blocked_request_policy_does_not_override_user_denial_outcome() { let service = NetworkApprovalService::default(); - register_test_call(&service, "registration-1").await; + register_call_with_default_shell_trigger(&service, "registration-1").await; service .record_call_outcome("registration-1", NetworkApprovalOutcome::DeniedByUser) @@ -300,8 +303,8 @@ async fn blocked_request_policy_does_not_override_user_denial_outcome() { #[tokio::test] async fn record_blocked_request_ignores_ambiguous_unattributed_blocked_requests() { let service = NetworkApprovalService::default(); - register_test_call(&service, "registration-1").await; - register_test_call(&service, "registration-2").await; + register_call_with_default_shell_trigger(&service, "registration-1").await; + register_call_with_default_shell_trigger(&service, "registration-2").await; service .record_blocked_request(denied_blocked_request("example.com")) From 82b246297caffc84076c862caa24f75edbccdbcf Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 21 Apr 2026 16:25:56 -0700 Subject: [PATCH 5/6] fix: keep network assessment exclusions explicit Co-authored-by: Codex --- codex-rs/core/src/guardian/approval_request.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codex-rs/core/src/guardian/approval_request.rs b/codex-rs/core/src/guardian/approval_request.rs index 93e458e02abc..5c72572645c4 100644 --- a/codex-rs/core/src/guardian/approval_request.rs +++ b/codex-rs/core/src/guardian/approval_request.rs @@ -357,11 +357,13 @@ pub(crate) fn guardian_assessment_action( } } GuardianApprovalRequest::NetworkAccess { + id: _id, + turn_id: _turn_id, target, host, protocol, port, - .. + trigger: _trigger, } => GuardianAssessmentAction::NetworkAccess { target: target.clone(), host: host.clone(), From 185f4040daf8790a204e23fe08e0404994ad7f06 Mon Sep 17 00:00:00 2001 From: viyatb-oai Date: Tue, 21 Apr 2026 17:07:26 -0700 Subject: [PATCH 6/6] fix: simplify network guardian trigger plumbing Co-authored-by: Codex --- codex-rs/core/src/tools/network_approval.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/codex-rs/core/src/tools/network_approval.rs b/codex-rs/core/src/tools/network_approval.rs index 37b4a04b09ed..2f2c4b60e717 100644 --- a/codex-rs/core/src/tools/network_approval.rs +++ b/codex-rs/core/src/tools/network_approval.rs @@ -425,11 +425,6 @@ impl NetworkApprovalService { } } let use_guardian = routes_approval_to_guardian(&turn_context); - let trigger = if use_guardian { - owner_call.as_ref().map(|call| call.trigger.clone()) - } else { - None - }; let guardian_review_id = use_guardian.then(new_guardian_review_id); let approval_decision = if let Some(review_id) = guardian_review_id.clone() { review_approval_request( @@ -445,7 +440,7 @@ impl NetworkApprovalService { host: request.host, protocol, port: key.port, - trigger, + trigger: owner_call.as_ref().map(|call| call.trigger.clone()), }, Some(policy_denial_message.clone()), )