Compare commits

...

8 Commits

Author SHA1 Message Date
viyatb-oai
e0c92bf446 Revert "fix(permissions): restore normal escalation semantics"
This reverts commit c8d96b6fb9.
2026-04-20 17:30:19 -07:00
viyatb-oai
f94dab4ddd Merge branch 'main' into codex/viyatb/deny-read-enforcement 2026-04-20 17:08:58 -07:00
viyatb-oai
c8d96b6fb9 fix(permissions): restore normal escalation semantics
Co-authored-by: Codex noreply@openai.com
2026-04-20 17:02:59 -07:00
viyatb-oai
7342b2c79d test(core): avoid known-safe command in exec-policy test
Co-authored-by: Codex noreply@openai.com
2026-04-20 16:53:33 -07:00
viyatb-oai
f4b4e67da5 fix(permissions): preserve exec-policy bypass semantics
Co-authored-by: Codex noreply@openai.com
2026-04-20 16:43:04 -07:00
viyatb-oai
cc48a2c830 fix(core): tighten deny-read rebase cleanup
Co-authored-by: Codex noreply@openai.com
2026-04-20 12:53:35 -07:00
viyatb-oai
639118ea20 fix(core): preserve deny-read during escalation
Keep approval/escalation flow intact while ensuring deny-read policies do not allow first-attempt sandbox bypass. Centralize the clamp in sandbox override selection and remove the special rejection helper.

Co-authored-by: Codex <noreply@openai.com>
2026-04-20 12:42:14 -07:00
viyatb-oai
44026eec82 fix(core): enforce explicit deny-read paths 2026-04-20 12:40:26 -07:00
6 changed files with 103 additions and 26 deletions

View File

@@ -1463,8 +1463,12 @@ async fn proposed_execpolicy_amendment_is_present_when_heuristics_allow() {
async fn proposed_execpolicy_amendment_is_suppressed_when_policy_matches_allow() {
assert_exec_approval_requirement_for_command(
ExecApprovalRequirementScenario {
policy_src: Some(r#"prefix_rule(pattern=["echo"], decision="allow")"#.to_string()),
command: vec!["echo".to_string(), "safe".to_string()],
policy_src: Some(r#"prefix_rule(pattern=["python3"], decision="allow")"#.to_string()),
command: vec![
"python3".to_string(),
"-c".to_string(),
"print(1)".to_string(),
],
approval_policy: AskForApproval::OnRequest,
sandbox_policy: SandboxPolicy::new_read_only_policy(),
file_system_sandbox_policy: read_only_file_system_sandbox_policy(),

View File

@@ -187,16 +187,17 @@ impl ToolOrchestrator {
// 2) First attempt under the selected sandbox.
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,
managed_network_active,
),
};
let initial_sandbox =
match tool.sandbox_mode_for_first_attempt(req, &turn_ctx.file_system_sandbox_policy) {
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,
managed_network_active,
),
};
// Platform-specific flag gating is handled by SandboxManager::select_initial.
let use_legacy_landlock = turn_ctx.features.use_legacy_landlock();

View File

@@ -35,6 +35,7 @@ use crate::tools::sandboxing::with_cached_approval;
use codex_network_proxy::NetworkProxy;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxablePreference;
use codex_shell_command::powershell::prefix_powershell_script_with_utf8;
@@ -207,8 +208,16 @@ impl Approvable<ShellRequest> for ShellRuntime {
})
}
fn sandbox_mode_for_first_attempt(&self, req: &ShellRequest) -> SandboxOverride {
sandbox_override_for_first_attempt(req.sandbox_permissions, &req.exec_approval_requirement)
fn sandbox_mode_for_first_attempt(
&self,
req: &ShellRequest,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> SandboxOverride {
sandbox_override_for_first_attempt(
req.sandbox_permissions,
&req.exec_approval_requirement,
file_system_sandbox_policy,
)
}
}

View File

@@ -38,6 +38,7 @@ use codex_network_proxy::NetworkProxy;
use codex_protocol::error::CodexErr;
use codex_protocol::error::SandboxErr;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::protocol::ReviewDecision;
use codex_sandboxing::SandboxablePreference;
use codex_shell_command::powershell::prefix_powershell_script_with_utf8;
@@ -192,8 +193,16 @@ impl Approvable<UnifiedExecRequest> for UnifiedExecRuntime<'_> {
})
}
fn sandbox_mode_for_first_attempt(&self, req: &UnifiedExecRequest) -> SandboxOverride {
sandbox_override_for_first_attempt(req.sandbox_permissions, &req.exec_approval_requirement)
fn sandbox_mode_for_first_attempt(
&self,
req: &UnifiedExecRequest,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> SandboxOverride {
sandbox_override_for_first_attempt(
req.sandbox_permissions,
&req.exec_approval_requirement,
file_system_sandbox_policy,
)
}
}

View File

@@ -229,18 +229,27 @@ pub(crate) enum SandboxOverride {
pub(crate) fn sandbox_override_for_first_attempt(
sandbox_permissions: SandboxPermissions,
exec_approval_requirement: &ExecApprovalRequirement,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> SandboxOverride {
// ExecPolicy `Allow` can intentionally imply full trust (Skip + bypass_sandbox=true),
// which supersedes `with_additional_permissions` sandboxed execution hints.
if sandbox_permissions.requires_escalated_permissions()
|| matches!(
exec_approval_requirement,
ExecApprovalRequirement::Skip {
bypass_sandbox: true,
..
}
)
{
if matches!(
exec_approval_requirement,
ExecApprovalRequirement::Skip {
bypass_sandbox: true,
..
}
) {
return SandboxOverride::BypassSandboxFirstAttempt;
}
// Deny-read restrictions suppress explicit escalation because that path
// would otherwise discard the filesystem policy entirely.
if file_system_sandbox_policy.has_denied_read_restrictions() {
return SandboxOverride::NoOverride;
}
if sandbox_permissions.requires_escalated_permissions() {
SandboxOverride::BypassSandboxFirstAttempt
} else {
SandboxOverride::NoOverride
@@ -262,7 +271,11 @@ pub(crate) trait Approvable<Req> {
/// Some tools may request to skip the sandbox on the first attempt
/// (e.g., when the request explicitly asks for escalated permissions).
/// Defaults to `NoOverride`.
fn sandbox_mode_for_first_attempt(&self, _req: &Req) -> SandboxOverride {
fn sandbox_mode_for_first_attempt(
&self,
_req: &Req,
_file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> SandboxOverride {
SandboxOverride::NoOverride
}

View File

@@ -1,5 +1,9 @@
use super::*;
use crate::sandboxing::SandboxPermissions;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_protocol::protocol::NetworkAccess;
use pretty_assertions::assert_eq;
@@ -90,6 +94,7 @@ fn additional_permissions_allow_bypass_sandbox_first_attempt_when_execpolicy_ski
bypass_sandbox: true,
proposed_execpolicy_amendment: None,
},
&FileSystemSandboxPolicy::default(),
),
SandboxOverride::BypassSandboxFirstAttempt
);
@@ -104,7 +109,43 @@ fn guardian_bypasses_sandbox_for_explicit_escalation_on_first_attempt() {
bypass_sandbox: false,
proposed_execpolicy_amendment: None,
},
&FileSystemSandboxPolicy::default(),
),
SandboxOverride::BypassSandboxFirstAttempt
);
}
#[test]
fn deny_read_blocks_explicit_escalation_but_preserves_policy_bypass() {
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::None,
}]);
assert_eq!(
sandbox_override_for_first_attempt(
SandboxPermissions::RequireEscalated,
&ExecApprovalRequirement::Skip {
bypass_sandbox: false,
proposed_execpolicy_amendment: None,
},
&file_system_policy,
),
SandboxOverride::NoOverride,
"explicit escalation would drop deny-read filesystem policy, so keep the first attempt sandboxed",
);
assert_eq!(
sandbox_override_for_first_attempt(
SandboxPermissions::WithAdditionalPermissions,
&ExecApprovalRequirement::Skip {
bypass_sandbox: true,
proposed_execpolicy_amendment: None,
},
&file_system_policy,
),
SandboxOverride::BypassSandboxFirstAttempt,
"exec-policy allow rules intentionally bypass sandbox even when deny-read entries exist",
);
}