diff --git a/codex-rs/core/src/context/permissions_instructions.rs b/codex-rs/core/src/context/permissions_instructions.rs index 0ccd6c33a7..5bb2b559e7 100644 --- a/codex-rs/core/src/context/permissions_instructions.rs +++ b/codex-rs/core/src/context/permissions_instructions.rs @@ -24,6 +24,7 @@ const APPROVAL_POLICY_ON_REQUEST_RULE: &str = const APPROVAL_POLICY_ON_REQUEST_RULE_REQUEST_PERMISSION: &str = include_str!("prompts/permissions/approval_policy/on_request_rule_request_permission.md"); const AUTO_REVIEW_APPROVAL_SUFFIX: &str = "`approvals_reviewer` is `auto_review`: Sandbox escalations with require_escalated will be reviewed for compliance with the policy. If a rejection happens, you should proceed only with a materially safer alternative, or inform the user of the risk and send a final message to ask for approval."; +const NETWORK_PROXY: &str = include_str!("prompts/permissions/network_proxy.md"); const SANDBOX_MODE_DANGER_FULL_ACCESS: &str = include_str!("prompts/permissions/sandbox_mode/danger_full_access.md"); @@ -50,6 +51,7 @@ struct PermissionsPromptConfig<'a> { exec_policy: &'a Policy, exec_permission_approvals_enabled: bool, request_permissions_tool_enabled: bool, + managed_network_proxy_active: bool, } #[derive(Debug, Clone, PartialEq)] @@ -68,6 +70,7 @@ impl PermissionsInstructions { cwd: &Path, exec_permission_approvals_enabled: bool, request_permissions_tool_enabled: bool, + managed_network_proxy_active: bool, ) -> Self { let (sandbox_mode, writable_roots) = sandbox_prompt_from_profile(permission_profile, cwd); @@ -80,6 +83,7 @@ impl PermissionsInstructions { exec_policy, exec_permission_approvals_enabled, request_permissions_tool_enabled, + managed_network_proxy_active, }, writable_roots, ) @@ -94,6 +98,7 @@ impl PermissionsInstructions { cwd: &Path, exec_permission_approvals_enabled: bool, request_permissions_tool_enabled: bool, + managed_network_proxy_active: bool, ) -> Self { Self::from_permission_profile( &PermissionProfile::from_legacy_sandbox_policy(sandbox_policy), @@ -103,6 +108,7 @@ impl PermissionsInstructions { cwd, exec_permission_approvals_enabled, request_permissions_tool_enabled, + managed_network_proxy_active, ) } @@ -114,6 +120,9 @@ impl PermissionsInstructions { ) -> Self { let mut text = String::new(); append_section(&mut text, &sandbox_text(sandbox_mode, network_access)); + if config.managed_network_proxy_active { + append_section(&mut text, NETWORK_PROXY.trim_end()); + } append_section( &mut text, &approval_text( diff --git a/codex-rs/core/src/context/permissions_instructions_tests.rs b/codex-rs/core/src/context/permissions_instructions_tests.rs index 16d5dc631a..c87e9ead3b 100644 --- a/codex-rs/core/src/context/permissions_instructions_tests.rs +++ b/codex-rs/core/src/context/permissions_instructions_tests.rs @@ -38,6 +38,7 @@ fn builds_permissions_with_network_access_override() { exec_policy: &Policy::empty(), exec_permission_approvals_enabled: false, request_permissions_tool_enabled: false, + managed_network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -70,6 +71,7 @@ fn builds_permissions_from_policy() { &PathBuf::from("/tmp"), /*exec_permission_approvals_enabled*/ false, /*request_permissions_tool_enabled*/ false, + /*managed_network_proxy_active*/ false, ); let text = instructions.body(); assert!(text.contains("Network access is enabled.")); @@ -99,6 +101,7 @@ fn builds_permissions_from_profile() { &cwd, /*exec_permission_approvals_enabled*/ false, /*request_permissions_tool_enabled*/ false, + /*managed_network_proxy_active*/ false, ); let text = instructions.body(); assert!(text.contains("`sandbox_mode` is `workspace-write`")); @@ -106,6 +109,47 @@ fn builds_permissions_from_profile() { assert!(text.contains(writable_root.to_string_lossy().as_ref())); } +#[test] +fn includes_network_proxy_guidance_when_managed_proxy_is_active() { + let instructions = PermissionsInstructions::from_permissions_with_network( + SandboxMode::WorkspaceWrite, + NetworkAccess::Restricted, + PermissionsPromptConfig { + approval_policy: AskForApproval::Never, + approvals_reviewer: ApprovalsReviewer::User, + exec_policy: &Policy::empty(), + exec_permission_approvals_enabled: false, + request_permissions_tool_enabled: false, + managed_network_proxy_active: true, + }, + /*writable_roots*/ None, + ); + + let text = instructions.body(); + assert!(text.contains("# Network Proxy")); + assert!(text.contains("blocked-by-allowlist")); + assert!(text.contains("proxy-specific headers or messages")); +} + +#[test] +fn omits_network_proxy_guidance_when_managed_proxy_is_inactive() { + let instructions = PermissionsInstructions::from_permissions_with_network( + SandboxMode::WorkspaceWrite, + NetworkAccess::Restricted, + PermissionsPromptConfig { + approval_policy: AskForApproval::Never, + approvals_reviewer: ApprovalsReviewer::User, + exec_policy: &Policy::empty(), + exec_permission_approvals_enabled: false, + request_permissions_tool_enabled: false, + managed_network_proxy_active: false, + }, + /*writable_roots*/ None, + ); + + assert!(!instructions.body().contains("# Network Proxy")); +} + #[test] fn includes_request_rule_instructions_for_on_request() { let mut exec_policy = Policy::empty(); @@ -121,6 +165,7 @@ fn includes_request_rule_instructions_for_on_request() { exec_policy: &exec_policy, exec_permission_approvals_enabled: false, request_permissions_tool_enabled: false, + managed_network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -142,6 +187,7 @@ fn includes_request_permissions_tool_instructions_for_unless_trusted_when_enable exec_policy: &Policy::empty(), exec_permission_approvals_enabled: false, request_permissions_tool_enabled: true, + managed_network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -162,6 +208,7 @@ fn includes_request_permissions_tool_instructions_for_on_failure_when_enabled() exec_policy: &Policy::empty(), exec_permission_approvals_enabled: false, request_permissions_tool_enabled: true, + managed_network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -182,6 +229,7 @@ fn includes_request_permission_rule_instructions_for_on_request_when_enabled() { exec_policy: &Policy::empty(), exec_permission_approvals_enabled: true, request_permissions_tool_enabled: false, + managed_network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -202,6 +250,7 @@ fn includes_request_permissions_tool_instructions_for_on_request_when_tool_is_en exec_policy: &Policy::empty(), exec_permission_approvals_enabled: false, request_permissions_tool_enabled: true, + managed_network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -222,6 +271,7 @@ fn on_request_includes_tool_guidance_alongside_inline_permission_guidance_when_b exec_policy: &Policy::empty(), exec_permission_approvals_enabled: true, request_permissions_tool_enabled: true, + managed_network_proxy_active: false, }, /*writable_roots*/ None, ); diff --git a/codex-rs/core/src/context/prompts/permissions/network_proxy.md b/codex-rs/core/src/context/prompts/permissions/network_proxy.md new file mode 100644 index 0000000000..8f8fe8ca05 --- /dev/null +++ b/codex-rs/core/src/context/prompts/permissions/network_proxy.md @@ -0,0 +1,14 @@ +# Network Proxy + +A managed network proxy is active for model-initiated shell commands. Codex applies proxy environment variables automatically so outbound traffic is checked against the configured network policy. + +Honor any `` allow/deny entries in the environment context. Use normal network tools without clearing or overriding proxy-related environment variables. If a required host is not allowed, request additional network permissions instead of working around the proxy. + +Interpret proxy failures precisely: +- `blocked-by-allowlist` means the host is not allowed by the current network policy. +- `blocked-by-denylist` means the host is explicitly denied by policy. +- `blocked-by-method-policy` means the current network mode disallows that request method. +- `blocked-by-mitm-required` means limited HTTPS cannot be enforced for that request without MITM support. +- A message about local/private network addresses means the sandbox is blocking local or private targets. + +Do not infer a proxy denial from a generic network failure alone. Timeouts, hangs, DNS errors, TLS errors, and connection failures are only evidence of proxy policy when accompanied by proxy-specific headers or messages. diff --git a/codex-rs/core/src/context_manager/updates.rs b/codex-rs/core/src/context_manager/updates.rs index 1bc2cb0895..8a2dfb0d54 100644 --- a/codex-rs/core/src/context_manager/updates.rs +++ b/codex-rs/core/src/context_manager/updates.rs @@ -64,6 +64,7 @@ fn build_permissions_update_item( &next.cwd, next.features.enabled(Feature::ExecPermissionApprovals), next.features.enabled(Feature::RequestPermissionsTool), + next.network.is_some(), ) .render(), ) diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index c45a8b638a..3cd8868445 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -2579,6 +2579,7 @@ impl Session { turn_context .features .enabled(Feature::RequestPermissionsTool), + turn_context.network.is_some(), ) .render(), ); diff --git a/codex-rs/core/tests/suite/permissions_messages.rs b/codex-rs/core/tests/suite/permissions_messages.rs index bb93d5cbf8..ca06b36c32 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -583,6 +583,7 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { test.config.cwd.as_path(), /*exec_permission_approvals_enabled*/ false, /*request_permissions_tool_enabled*/ false, + /*managed_network_proxy_active*/ false, ) .render(); let expected_normalized = normalize_line_endings(&expected);