diff --git a/codex-rs/core/src/context/mod.rs b/codex-rs/core/src/context/mod.rs index 25a6e1f1349d..7f70735ec12d 100644 --- a/codex-rs/core/src/context/mod.rs +++ b/codex-rs/core/src/context/mod.rs @@ -41,6 +41,7 @@ pub(crate) use hook_additional_context::HookAdditionalContext; pub(crate) use image_generation_instructions::ImageGenerationInstructions; pub(crate) use model_switch_instructions::ModelSwitchInstructions; pub(crate) use network_rule_saved::NetworkRuleSaved; +pub use permissions_instructions::PermissionsInstructionOptions; pub use permissions_instructions::PermissionsInstructions; pub(crate) use personality_spec_instructions::PersonalitySpecInstructions; pub(crate) use plugin_instructions::PluginInstructions; diff --git a/codex-rs/core/src/context/permissions_instructions.rs b/codex-rs/core/src/context/permissions_instructions.rs index 0ccd6c33a731..93b72051a8db 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,15 @@ struct PermissionsPromptConfig<'a> { exec_policy: &'a Policy, exec_permission_approvals_enabled: bool, request_permissions_tool_enabled: bool, + network_proxy_active: bool, +} + +#[derive(Debug, Clone, Copy)] +/// Feature-dependent knobs used while rendering permissions instructions. +pub struct PermissionsInstructionOptions { + pub exec_permission_approvals_enabled: bool, + pub request_permissions_tool_enabled: bool, + pub network_proxy_active: bool, } #[derive(Debug, Clone, PartialEq)] @@ -66,8 +76,7 @@ impl PermissionsInstructions { approvals_reviewer: ApprovalsReviewer, exec_policy: &Policy, cwd: &Path, - exec_permission_approvals_enabled: bool, - request_permissions_tool_enabled: bool, + options: PermissionsInstructionOptions, ) -> Self { let (sandbox_mode, writable_roots) = sandbox_prompt_from_profile(permission_profile, cwd); @@ -78,8 +87,9 @@ impl PermissionsInstructions { approval_policy, approvals_reviewer, exec_policy, - exec_permission_approvals_enabled, - request_permissions_tool_enabled, + exec_permission_approvals_enabled: options.exec_permission_approvals_enabled, + request_permissions_tool_enabled: options.request_permissions_tool_enabled, + network_proxy_active: options.network_proxy_active, }, writable_roots, ) @@ -92,8 +102,7 @@ impl PermissionsInstructions { approvals_reviewer: ApprovalsReviewer, exec_policy: &Policy, cwd: &Path, - exec_permission_approvals_enabled: bool, - request_permissions_tool_enabled: bool, + options: PermissionsInstructionOptions, ) -> Self { Self::from_permission_profile( &PermissionProfile::from_legacy_sandbox_policy(sandbox_policy), @@ -101,8 +110,7 @@ impl PermissionsInstructions { approvals_reviewer, exec_policy, cwd, - exec_permission_approvals_enabled, - request_permissions_tool_enabled, + options, ) } @@ -114,6 +122,9 @@ impl PermissionsInstructions { ) -> Self { let mut text = String::new(); append_section(&mut text, &sandbox_text(sandbox_mode, network_access)); + if config.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 16d5dc631aee..b8e076db5890 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, + network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -68,8 +69,11 @@ fn builds_permissions_from_policy() { ApprovalsReviewer::User, &Policy::empty(), &PathBuf::from("/tmp"), - /*exec_permission_approvals_enabled*/ false, - /*request_permissions_tool_enabled*/ false, + PermissionsInstructionOptions { + exec_permission_approvals_enabled: false, + request_permissions_tool_enabled: false, + network_proxy_active: false, + }, ); let text = instructions.body(); assert!(text.contains("Network access is enabled.")); @@ -97,8 +101,11 @@ fn builds_permissions_from_profile() { ApprovalsReviewer::User, &Policy::empty(), &cwd, - /*exec_permission_approvals_enabled*/ false, - /*request_permissions_tool_enabled*/ false, + PermissionsInstructionOptions { + exec_permission_approvals_enabled: false, + request_permissions_tool_enabled: false, + network_proxy_active: false, + }, ); let text = instructions.body(); assert!(text.contains("`sandbox_mode` is `workspace-write`")); @@ -106,6 +113,47 @@ fn builds_permissions_from_profile() { assert!(text.contains(writable_root.to_string_lossy().as_ref())); } +#[test] +fn includes_network_proxy_guidance_when_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, + 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_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, + 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 +169,7 @@ fn includes_request_rule_instructions_for_on_request() { exec_policy: &exec_policy, exec_permission_approvals_enabled: false, request_permissions_tool_enabled: false, + network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -142,6 +191,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, + network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -162,6 +212,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, + network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -182,6 +233,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, + network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -202,6 +254,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, + network_proxy_active: false, }, /*writable_roots*/ None, ); @@ -222,6 +275,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, + 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 000000000000..5e8eaaef9201 --- /dev/null +++ b/codex-rs/core/src/context/prompts/permissions/network_proxy.md @@ -0,0 +1,12 @@ +# Network Proxy + +A 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. +- 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. Proxy-mediated requests can themselves time out or hang. Treat timeouts, hangs, DNS errors, TLS errors, and connection failures as evidence of proxy policy only when they also include 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 1bc2cb0895a5..1ea533098d37 100644 --- a/codex-rs/core/src/context_manager/updates.rs +++ b/codex-rs/core/src/context_manager/updates.rs @@ -2,6 +2,7 @@ use crate::context::CollaborationModeInstructions; use crate::context::ContextualUserFragment; use crate::context::EnvironmentContext; use crate::context::ModelSwitchInstructions; +use crate::context::PermissionsInstructionOptions; use crate::context::PermissionsInstructions; use crate::context::PersonalitySpecInstructions; use crate::context::RealtimeEndInstructions; @@ -62,8 +63,15 @@ fn build_permissions_update_item( next.config.approvals_reviewer, exec_policy, &next.cwd, - next.features.enabled(Feature::ExecPermissionApprovals), - next.features.enabled(Feature::RequestPermissionsTool), + PermissionsInstructionOptions { + exec_permission_approvals_enabled: next + .features + .enabled(Feature::ExecPermissionApprovals), + request_permissions_tool_enabled: next + .features + .enabled(Feature::RequestPermissionsTool), + network_proxy_active: next.network.is_some(), + }, ) .render(), ) diff --git a/codex-rs/core/src/session/mod.rs b/codex-rs/core/src/session/mod.rs index c45a8b638a5f..2b635d92f2d3 100644 --- a/codex-rs/core/src/session/mod.rs +++ b/codex-rs/core/src/session/mod.rs @@ -27,6 +27,7 @@ use crate::context::AvailableSkillsInstructions; use crate::context::CollaborationModeInstructions; use crate::context::ContextualUserFragment; use crate::context::NetworkRuleSaved; +use crate::context::PermissionsInstructionOptions; use crate::context::PermissionsInstructions; use crate::context::PersonalitySpecInstructions; use crate::default_skill_metadata_budget; @@ -2573,12 +2574,15 @@ impl Session { turn_context.config.approvals_reviewer, self.services.exec_policy.current().as_ref(), &turn_context.cwd, - turn_context - .features - .enabled(Feature::ExecPermissionApprovals), - turn_context - .features - .enabled(Feature::RequestPermissionsTool), + PermissionsInstructionOptions { + exec_permission_approvals_enabled: turn_context + .features + .enabled(Feature::ExecPermissionApprovals), + request_permissions_tool_enabled: turn_context + .features + .enabled(Feature::RequestPermissionsTool), + network_proxy_active: 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 bb93d5cbf86f..86501be63317 100644 --- a/codex-rs/core/tests/suite/permissions_messages.rs +++ b/codex-rs/core/tests/suite/permissions_messages.rs @@ -3,6 +3,7 @@ use codex_config::ConfigLayerStack; use codex_core::ForkSnapshot; use codex_core::config::Constrained; use codex_core::context::ContextualUserFragment; +use codex_core::context::PermissionsInstructionOptions; use codex_core::context::PermissionsInstructions; use codex_core::load_exec_policy; use codex_protocol::models::PermissionProfile; @@ -581,8 +582,11 @@ async fn permissions_message_includes_writable_roots() -> Result<()> { test.config.approvals_reviewer, &exec_policy, test.config.cwd.as_path(), - /*exec_permission_approvals_enabled*/ false, - /*request_permissions_tool_enabled*/ false, + PermissionsInstructionOptions { + exec_permission_approvals_enabled: false, + request_permissions_tool_enabled: false, + network_proxy_active: test.session_configured.network_proxy.is_some(), + }, ) .render(); let expected_normalized = normalize_line_endings(&expected);