Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions codex-rs/core/src/codex_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,117 @@ async fn danger_full_access_turns_do_not_expose_managed_network_proxy() -> anyho
Ok(())
}

#[tokio::test]
async fn danger_full_access_tool_attempts_do_not_enforce_managed_network() -> anyhow::Result<()> {
#[derive(Default)]
struct ProbeToolRuntime {
enforce_managed_network: Vec<bool>,
}

impl crate::tools::sandboxing::Approvable<()> for ProbeToolRuntime {
type ApprovalKey = String;

fn approval_keys(&self, _req: &()) -> Vec<Self::ApprovalKey> {
vec!["probe".to_string()]
}

fn start_approval_async<'a>(
&'a mut self,
_req: &'a (),
_ctx: crate::tools::sandboxing::ApprovalCtx<'a>,
) -> futures::future::BoxFuture<'a, ReviewDecision> {
Box::pin(async { ReviewDecision::Approved })
}
}

impl crate::tools::sandboxing::Sandboxable for ProbeToolRuntime {
fn sandbox_preference(&self) -> codex_sandboxing::SandboxablePreference {
codex_sandboxing::SandboxablePreference::Auto
}
}

impl crate::tools::sandboxing::ToolRuntime<(), ()> for ProbeToolRuntime {
async fn run(
&mut self,
_req: &(),
attempt: &crate::tools::sandboxing::SandboxAttempt<'_>,
_ctx: &crate::tools::sandboxing::ToolCtx,
) -> Result<(), crate::tools::sandboxing::ToolError> {
self.enforce_managed_network
.push(attempt.enforce_managed_network);
Ok(())
}
}

let network_spec = crate::config::NetworkProxySpec::from_config_and_constraints(
NetworkProxyConfig::default(),
Some(NetworkConstraints {
enabled: Some(true),
..Default::default()
}),
&SandboxPolicy::DangerFullAccess,
)?;

let session = make_session_with_config(move |config| {
config.permissions.sandbox_policy =
codex_config::Constrained::allow_any(SandboxPolicy::DangerFullAccess);
config.permissions.network = Some(network_spec);

let layers = config
.config_layer_stack
.get_layers(
ConfigLayerStackOrdering::LowestPrecedenceFirst,
/*include_disabled*/ true,
)
.into_iter()
.cloned()
.collect();
let mut requirements = config.config_layer_stack.requirements().clone();
requirements.network = Some(Sourced::new(
NetworkConstraints {
enabled: Some(true),
..Default::default()
},
RequirementSource::CloudRequirements,
));
let mut requirements_toml = config.config_layer_stack.requirements_toml().clone();
requirements_toml.network = Some(crate::config_loader::NetworkRequirementsToml {
enabled: Some(true),
..Default::default()
});
config.config_layer_stack = ConfigLayerStack::new(layers, requirements, requirements_toml)
.expect("rebuild config layer stack with network requirements");
})
.await?;

let turn = session.new_default_turn().await;
assert!(turn.network.is_none());

let mut orchestrator = crate::tools::orchestrator::ToolOrchestrator::new();
let mut tool = ProbeToolRuntime::default();
let tool_ctx = crate::tools::sandboxing::ToolCtx {
session: Arc::clone(&session),
turn: Arc::clone(&turn),
call_id: "probe-call".to_string(),
tool_name: "probe".to_string(),
};

orchestrator
.run(
&mut tool,
&(),
&tool_ctx,
turn.as_ref(),
AskForApproval::Never,
)
.await
.expect("probe runtime should succeed");

assert_eq!(tool.enforce_managed_network, vec![false]);

Ok(())
}

#[tokio::test]
async fn workspace_write_turns_continue_to_expose_managed_network_proxy() -> anyhow::Result<()> {
let sandbox_policy = SandboxPolicy::new_workspace_write_policy();
Expand Down
11 changes: 3 additions & 8 deletions codex-rs/core/src/tools/js_repl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,18 +1033,13 @@ impl JsReplManager {
}

let sandbox = SandboxManager::new();
let has_managed_network_requirements = turn
.config
.config_layer_stack
.requirements_toml()
.network
.is_some();
let managed_network_active = turn.network.is_some();
let sandbox_type = sandbox.select_initial(
&turn.file_system_sandbox_policy,
turn.network_sandbox_policy,
SandboxablePreference::Auto,
turn.windows_sandbox_level,
has_managed_network_requirements,
managed_network_active,
);
let command = SandboxCommand {
program: node_path.into_os_string(),
Expand All @@ -1067,7 +1062,7 @@ impl JsReplManager {
file_system_policy: &turn.file_system_sandbox_policy,
network_policy: turn.network_sandbox_policy,
sandbox: sandbox_type,
enforce_managed_network: has_managed_network_requirements,
enforce_managed_network: managed_network_active,
network: None,
sandbox_policy_cwd: &turn.cwd,
codex_linux_sandbox_exe: turn.codex_linux_sandbox_exe.as_deref(),
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/core/src/tools/network_approval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,11 +578,11 @@ pub(crate) fn build_network_policy_decider(
pub(crate) async fn begin_network_approval(
session: &Session,
turn_id: &str,
has_managed_network_requirements: bool,
managed_network_active: bool,
spec: Option<NetworkApprovalSpec>,
) -> Option<ActiveNetworkApproval> {
let spec = spec?;
if !has_managed_network_requirements || spec.network.is_none() {
if !managed_network_active || spec.network.is_none() {
return None;
}

Expand Down
23 changes: 9 additions & 14 deletions codex-rs/core/src/tools/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ impl ToolOrchestrator {
req: &Rq,
tool_ctx: &ToolCtx,
attempt: &SandboxAttempt<'_>,
has_managed_network_requirements: bool,
managed_network_active: bool,
) -> (Result<Out, ToolError>, Option<DeferredNetworkApproval>)
where
T: ToolRuntime<Rq, Out>,
{
let network_approval = begin_network_approval(
&tool_ctx.session,
&tool_ctx.turn.sub_id,
has_managed_network_requirements,
managed_network_active,
tool.network_approval_spec(req, tool_ctx),
)
.await;
Expand Down Expand Up @@ -180,20 +180,15 @@ impl ToolOrchestrator {
}

// 2) First attempt under the selected sandbox.
let has_managed_network_requirements = turn_ctx
.config
.config_layer_stack
.requirements_toml()
.network
.is_some();
let managed_network_active = turn_ctx.network.is_some();
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
SandboxOverride::BypassSandboxFirstAttempt => SandboxType::None,
SandboxOverride::NoOverride => self.sandbox.select_initial(
&turn_ctx.file_system_sandbox_policy,
turn_ctx.network_sandbox_policy,
tool.sandbox_preference(),
turn_ctx.windows_sandbox_level,
has_managed_network_requirements,
managed_network_active,
),
};

Expand All @@ -204,7 +199,7 @@ impl ToolOrchestrator {
policy: &turn_ctx.sandbox_policy,
file_system_policy: &turn_ctx.file_system_sandbox_policy,
network_policy: turn_ctx.network_sandbox_policy,
enforce_managed_network: has_managed_network_requirements,
enforce_managed_network: managed_network_active,
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(),
Expand All @@ -221,7 +216,7 @@ impl ToolOrchestrator {
req,
tool_ctx,
&initial_attempt,
has_managed_network_requirements,
managed_network_active,
)
.await;
match first_result {
Expand All @@ -236,7 +231,7 @@ impl ToolOrchestrator {
output,
network_policy_decision,
}))) => {
let network_approval_context = if has_managed_network_requirements {
let network_approval_context = if managed_network_active {
network_policy_decision
.as_ref()
.and_then(network_approval_context_from_payload)
Expand Down Expand Up @@ -341,7 +336,7 @@ impl ToolOrchestrator {
policy: &turn_ctx.sandbox_policy,
file_system_policy: &turn_ctx.file_system_sandbox_policy,
network_policy: turn_ctx.network_sandbox_policy,
enforce_managed_network: has_managed_network_requirements,
enforce_managed_network: managed_network_active,
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
codex_linux_sandbox_exe: None,
Expand All @@ -359,7 +354,7 @@ impl ToolOrchestrator {
req,
tool_ctx,
&escalated_attempt,
has_managed_network_requirements,
managed_network_active,
)
.await;
retry_result.map(|output| OrchestratorRunResult {
Expand Down
Loading