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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions codex-rs/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ falls back to the vendored bubblewrap path otherwise. When `/usr/bin/bwrap` is
missing, Codex also surfaces a startup warning through its normal notification
path instead of printing directly from the sandbox helper.

### Windows

Legacy `SandboxPolicy` / `sandbox_mode` configs are still supported on
Windows.

The elevated setup/runner backend supports legacy `ReadOnlyAccess::Restricted`
for `read-only` and `workspace-write` policies. Restricted read access honors
explicit readable roots plus the command `cwd`, and keeps writable roots
readable when `workspace-write` is used.

When `include_platform_defaults = true`, the elevated Windows backend adds
backend-managed system read roots required for basic execution, such as
`C:\Windows`, `C:\Program Files`, `C:\Program Files (x86)`, and
`C:\ProgramData`. When it is `false`, those extra system roots are omitted.

The unelevated restricted-token backend still supports the legacy full-read
Windows model only. Restricted read-only policies continue to fail closed there
instead of running with weaker read enforcement.

New `[permissions]` / split filesystem policies remain supported on Windows
only when they round-trip through the legacy `SandboxPolicy` model without
changing semantics. Richer split-only carveouts still fail closed instead of
running with weaker enforcement.

### All Platforms

Expects the binary containing `codex-core` to simulate the virtual `apply_patch` CLI when `arg1` is `--codex-run-as-apply-patch`. See the `codex-arg0` crate for details.
68 changes: 43 additions & 25 deletions codex-rs/core/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use crate::spawn::spawn_child_async;
use crate::text_encoding::bytes_to_string_smart;
use crate::tools::sandboxing::SandboxablePreference;
use codex_network_proxy::NetworkProxy;
#[cfg(any(target_os = "windows", test))]
use codex_protocol::permissions::FileSystemSandboxKind;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::NetworkSandboxPolicy;
Expand Down Expand Up @@ -765,12 +766,14 @@ async fn exec(
) -> Result<RawExecToolCallOutput> {
#[cfg(target_os = "windows")]
if sandbox == SandboxType::WindowsRestrictedToken {
if let Some(reason) = unsupported_windows_restricted_token_sandbox_reason(
let support = windows_restricted_token_sandbox_support(
sandbox,
params.windows_sandbox_level,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
) {
);
if let Some(reason) = support.unsupported_reason {
return Err(CodexErr::Io(io::Error::other(reason)));
}
return exec_windows_sandbox(params, sandbox_policy).await;
Expand Down Expand Up @@ -817,41 +820,56 @@ async fn exec(
}

#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
fn should_use_windows_restricted_token_sandbox(
sandbox: SandboxType,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> bool {
sandbox == SandboxType::WindowsRestrictedToken
&& file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted
&& !matches!(
sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
)
#[derive(Debug, PartialEq, Eq)]
struct WindowsRestrictedTokenSandboxSupport {
should_use: bool,
unsupported_reason: Option<String>,
}

#[cfg(any(target_os = "windows", test))]
fn unsupported_windows_restricted_token_sandbox_reason(
fn windows_restricted_token_sandbox_support(
sandbox: SandboxType,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
) -> Option<String> {
if should_use_windows_restricted_token_sandbox(
sandbox,
sandbox_policy,
file_system_sandbox_policy,
) {
return None;
) -> WindowsRestrictedTokenSandboxSupport {
if sandbox != SandboxType::WindowsRestrictedToken {
return WindowsRestrictedTokenSandboxSupport {
should_use: false,
unsupported_reason: None,
};
}

(sandbox == SandboxType::WindowsRestrictedToken).then(|| {
format!(
// Windows currently reuses SandboxType::WindowsRestrictedToken for both
// the legacy restricted-token backend and the elevated setup/runner path.
// The sandbox level decides whether restricted read-only policies are
// supported.
let should_use = file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted
&& !matches!(
sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
)
&& (matches!(
windows_sandbox_level,
codex_protocol::config_types::WindowsSandboxLevel::Elevated
) || sandbox_policy.has_full_disk_read_access());

let unsupported_reason = if should_use {
None
} else {
Some(format!(
"windows sandbox backend cannot enforce file_system={:?}, network={network_sandbox_policy:?}, legacy_policy={sandbox_policy:?}; refusing to run unsandboxed",
file_system_sandbox_policy.kind,
)
})
))
};

WindowsRestrictedTokenSandboxSupport {
should_use,
unsupported_reason,
}
}

/// Consumes the output of a child process, truncating it so it is suitable for
/// use as the output of a `shell` tool call. Also enforces specified timeout.
async fn consume_truncated_output(
Expand Down
121 changes: 101 additions & 20 deletions codex-rs/core/src/exec_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::*;
use codex_protocol::config_types::WindowsSandboxLevel;
use pretty_assertions::assert_eq;
use std::time::Duration;
use tokio::io::AsyncWriteExt;
Expand Down Expand Up @@ -188,12 +189,19 @@ fn windows_restricted_token_skips_external_sandbox_policies() {
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![]);

assert_eq!(
should_use_windows_restricted_token_sandbox(
windows_restricted_token_sandbox_support(
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Disabled,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
false
WindowsRestrictedTokenSandboxSupport {
should_use: false,
unsupported_reason: Some(
"windows sandbox backend cannot enforce file_system=Restricted, network=Restricted, legacy_policy=ExternalSandbox { network_access: Restricted }; refusing to run unsandboxed".to_string()
),
}
);
}

Expand All @@ -203,12 +211,17 @@ fn windows_restricted_token_runs_for_legacy_restricted_policies() {
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![]);

assert_eq!(
should_use_windows_restricted_token_sandbox(
windows_restricted_token_sandbox_support(
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Disabled,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
true
WindowsRestrictedTokenSandboxSupport {
should_use: true,
unsupported_reason: None,
}
);
}

Expand All @@ -220,16 +233,20 @@ fn windows_restricted_token_rejects_network_only_restrictions() {
let file_system_policy = FileSystemSandboxPolicy::unrestricted();

assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
SandboxType::WindowsRestrictedToken,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
Some(
windows_restricted_token_sandbox_support(
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Disabled,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
WindowsRestrictedTokenSandboxSupport {
should_use: false,
unsupported_reason: Some(
"windows sandbox backend cannot enforce file_system=Unrestricted, network=Restricted, legacy_policy=ExternalSandbox { network_access: Restricted }; refusing to run unsandboxed".to_string()
)
);
),
}
);
}

#[test]
Expand All @@ -238,13 +255,46 @@ fn windows_restricted_token_allows_legacy_restricted_policies() {
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![]);

assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
windows_restricted_token_sandbox_support(
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Disabled,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
None
WindowsRestrictedTokenSandboxSupport {
should_use: true,
unsupported_reason: None,
}
);
}

#[test]
fn windows_restricted_token_rejects_restricted_read_only_policies() {
let policy = SandboxPolicy::ReadOnly {
access: codex_protocol::protocol::ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![],
},
network_access: false,
};
let file_system_policy = FileSystemSandboxPolicy::from(&policy);

assert_eq!(
windows_restricted_token_sandbox_support(
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Disabled,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
WindowsRestrictedTokenSandboxSupport {
should_use: false,
unsupported_reason: Some(
"windows sandbox backend cannot enforce file_system=Restricted, network=Restricted, legacy_policy=ReadOnly { access: Restricted { include_platform_defaults: true, readable_roots: [] }, network_access: false }; refusing to run unsandboxed".to_string()
),
},
"restricted-token should fail closed for restricted read-only policies"
);
}

Expand All @@ -260,13 +310,44 @@ fn windows_restricted_token_allows_legacy_workspace_write_policies() {
let file_system_policy = FileSystemSandboxPolicy::from(&policy);

assert_eq!(
unsupported_windows_restricted_token_sandbox_reason(
windows_restricted_token_sandbox_support(
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Disabled,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
WindowsRestrictedTokenSandboxSupport {
should_use: true,
unsupported_reason: None,
}
);
}

#[test]
fn windows_elevated_sandbox_allows_restricted_read_only_policies() {
let policy = SandboxPolicy::ReadOnly {
access: codex_protocol::protocol::ReadOnlyAccess::Restricted {
include_platform_defaults: true,
readable_roots: vec![],
},
network_access: false,
};
let file_system_policy = FileSystemSandboxPolicy::from(&policy);

assert_eq!(
windows_restricted_token_sandbox_support(
SandboxType::WindowsRestrictedToken,
WindowsSandboxLevel::Elevated,
&policy,
&file_system_policy,
NetworkSandboxPolicy::Restricted,
),
None
WindowsRestrictedTokenSandboxSupport {
should_use: true,
unsupported_reason: None,
},
"elevated Windows sandbox should keep restricted read-only support enabled"
);
}

Expand All @@ -278,7 +359,7 @@ fn process_exec_tool_call_uses_platform_sandbox_for_network_only_restrictions()
select_process_exec_tool_sandbox_type(
&FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Restricted,
codex_protocol::config_types::WindowsSandboxLevel::Disabled,
WindowsSandboxLevel::Disabled,
false,
),
expected
Expand Down Expand Up @@ -318,7 +399,7 @@ async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()>
env,
network: None,
sandbox_permissions: SandboxPermissions::UseDefault,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
justification: None,
arg0: None,
Expand Down Expand Up @@ -375,7 +456,7 @@ async fn process_exec_tool_call_respects_cancellation_token() -> Result<()> {
env,
network: None,
sandbox_permissions: SandboxPermissions::UseDefault,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
justification: None,
arg0: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,6 @@ fn spawn_ipc_process(
);

let policy = parse_policy(&req.policy_json_or_preset).context("parse policy_json_or_preset")?;
if !policy.has_full_disk_read_access() {
anyhow::bail!(
"Restricted read-only access is not yet supported by the Windows sandbox backend"
);
}
let mut cap_psids: Vec<*mut c_void> = Vec::new();
for sid in &req.cap_sids {
let Some(psid) = (unsafe { convert_string_sid_to_sid(sid) }) else {
Expand Down
5 changes: 0 additions & 5 deletions codex-rs/windows-sandbox-rs/src/elevated_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,6 @@ mod windows_impl {
) {
anyhow::bail!("DangerFullAccess and ExternalSandbox are not supported for sandboxing")
}
if !policy.has_full_disk_read_access() {
anyhow::bail!(
"Restricted read-only access is not yet supported by the Windows sandbox backend"
);
}
let caps = load_or_create_cap_sids(codex_home)?;
let (psid_to_use, cap_sids) = match &policy {
SandboxPolicy::ReadOnly { .. } => (
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/windows-sandbox-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ mod windows_impl {
}
if !policy.has_full_disk_read_access() {
anyhow::bail!(
"Restricted read-only access is not yet supported by the Windows sandbox backend"
"Restricted read-only access requires the elevated Windows sandbox backend"
);
}
let caps = load_or_create_cap_sids(codex_home)?;
Expand Down
Loading
Loading