mirror of
https://github.com/openai/codex.git
synced 2026-10-28 08:04:55 +00:00
## Why Permission profiles can mark filesystem entries as unreadable with `deny` rules, including glob patterns. Several shell execution paths treated known-safe commands or execpolicy `allow` rules as sufficient to run outside the filesystem sandbox. That is not valid for read-capable commands: for example, `cat` or `ls` may be reasonable to allow generally, but dropping the sandbox would also drop deny-read constraints such as `**/*.env`. ## What changed - Added a shared check that treats active deny-read restrictions as incompatible with unsandboxed execution. - Kept first-attempt execution sandboxed for explicit escalation and execpolicy allow bypasses when deny-read entries are present. - Prevented no-sandbox retry after a sandbox denial when the active filesystem policy contains deny-read entries. - Updated the zsh-fork execve path so prefix-rule `allow` decisions continue inside the current sandbox when deny-read restrictions are active. ## Verification - `cargo test -p codex-core tools::sandboxing::tests` - `cargo test -p codex-core tools::runtimes::shell::unix_escalation::tests` - `cargo test -p codex-core shell_command_enforces_glob_deny_read_policy`
196 lines
6.0 KiB
Rust
196 lines
6.0 KiB
Rust
use super::*;
|
|
use crate::sandboxing::SandboxPermissions;
|
|
use crate::tools::hook_names::HookToolName;
|
|
use codex_protocol::permissions::FileSystemAccessMode;
|
|
use codex_protocol::permissions::FileSystemPath;
|
|
use codex_protocol::permissions::FileSystemSandboxEntry;
|
|
use codex_protocol::protocol::GranularApprovalConfig;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn bash_permission_request_payload_omits_missing_description() {
|
|
assert_eq!(
|
|
PermissionRequestPayload::bash("echo hi".to_string(), /*description*/ None),
|
|
PermissionRequestPayload {
|
|
tool_name: HookToolName::bash(),
|
|
tool_input: json!({ "command": "echo hi" }),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bash_permission_request_payload_includes_description_when_present() {
|
|
assert_eq!(
|
|
PermissionRequestPayload::bash(
|
|
"echo hi".to_string(),
|
|
Some("network-access example.com".to_string()),
|
|
),
|
|
PermissionRequestPayload {
|
|
tool_name: HookToolName::bash(),
|
|
tool_input: json!({
|
|
"command": "echo hi",
|
|
"description": "network-access example.com",
|
|
}),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn external_sandbox_skips_exec_approval_on_request() {
|
|
assert_eq!(
|
|
default_exec_approval_requirement(
|
|
AskForApproval::OnRequest,
|
|
&FileSystemSandboxPolicy::external_sandbox(),
|
|
),
|
|
ExecApprovalRequirement::Skip {
|
|
bypass_sandbox: false,
|
|
proposed_execpolicy_amendment: None,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn restricted_sandbox_requires_exec_approval_on_request() {
|
|
assert_eq!(
|
|
default_exec_approval_requirement(
|
|
AskForApproval::OnRequest,
|
|
&FileSystemSandboxPolicy::default()
|
|
),
|
|
ExecApprovalRequirement::NeedsApproval {
|
|
reason: None,
|
|
proposed_execpolicy_amendment: None,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn default_exec_approval_requirement_rejects_sandbox_prompt_when_granular_disables_it() {
|
|
let policy = AskForApproval::Granular(GranularApprovalConfig {
|
|
sandbox_approval: false,
|
|
rules: true,
|
|
skill_approval: true,
|
|
request_permissions: true,
|
|
mcp_elicitations: true,
|
|
});
|
|
|
|
let requirement =
|
|
default_exec_approval_requirement(policy, &FileSystemSandboxPolicy::default());
|
|
|
|
assert_eq!(
|
|
requirement,
|
|
ExecApprovalRequirement::Forbidden {
|
|
reason: "approval policy disallowed sandbox approval prompt".to_string(),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn default_exec_approval_requirement_keeps_prompt_when_granular_allows_sandbox_approval() {
|
|
let policy = AskForApproval::Granular(GranularApprovalConfig {
|
|
sandbox_approval: true,
|
|
rules: false,
|
|
skill_approval: true,
|
|
request_permissions: true,
|
|
mcp_elicitations: false,
|
|
});
|
|
|
|
let requirement =
|
|
default_exec_approval_requirement(policy, &FileSystemSandboxPolicy::default());
|
|
|
|
assert_eq!(
|
|
requirement,
|
|
ExecApprovalRequirement::NeedsApproval {
|
|
reason: None,
|
|
proposed_execpolicy_amendment: None,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn additional_permissions_allow_bypass_sandbox_first_attempt_when_execpolicy_skips() {
|
|
assert_eq!(
|
|
sandbox_override_for_first_attempt(
|
|
SandboxPermissions::WithAdditionalPermissions,
|
|
&ExecApprovalRequirement::Skip {
|
|
bypass_sandbox: true,
|
|
proposed_execpolicy_amendment: None,
|
|
},
|
|
&FileSystemSandboxPolicy::default(),
|
|
),
|
|
SandboxOverride::BypassSandboxFirstAttempt
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn guardian_bypasses_sandbox_for_explicit_escalation_on_first_attempt() {
|
|
assert_eq!(
|
|
sandbox_override_for_first_attempt(
|
|
SandboxPermissions::RequireEscalated,
|
|
&ExecApprovalRequirement::Skip {
|
|
bypass_sandbox: false,
|
|
proposed_execpolicy_amendment: None,
|
|
},
|
|
&FileSystemSandboxPolicy::default(),
|
|
),
|
|
SandboxOverride::BypassSandboxFirstAttempt
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn deny_read_blocks_explicit_escalation_and_policy_bypass() {
|
|
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
|
path: FileSystemPath::GlobPattern {
|
|
pattern: "**/*.env".to_string(),
|
|
},
|
|
access: FileSystemAccessMode::Deny,
|
|
}]);
|
|
|
|
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!(!unsandboxed_execution_allowed(&file_system_policy));
|
|
assert_eq!(
|
|
sandbox_permissions_preserving_denied_reads(
|
|
SandboxPermissions::RequireEscalated,
|
|
&file_system_policy,
|
|
),
|
|
SandboxPermissions::UseDefault,
|
|
);
|
|
assert_eq!(
|
|
sandbox_permissions_preserving_denied_reads(
|
|
SandboxPermissions::WithAdditionalPermissions,
|
|
&file_system_policy,
|
|
),
|
|
SandboxPermissions::WithAdditionalPermissions,
|
|
);
|
|
assert_eq!(
|
|
sandbox_permissions_preserving_denied_reads(
|
|
SandboxPermissions::RequireEscalated,
|
|
&FileSystemSandboxPolicy::default(),
|
|
),
|
|
SandboxPermissions::RequireEscalated,
|
|
);
|
|
assert_eq!(
|
|
sandbox_override_for_first_attempt(
|
|
SandboxPermissions::WithAdditionalPermissions,
|
|
&ExecApprovalRequirement::Skip {
|
|
bypass_sandbox: true,
|
|
proposed_execpolicy_amendment: None,
|
|
},
|
|
&file_system_policy,
|
|
),
|
|
SandboxOverride::NoOverride,
|
|
"exec-policy allow rules would drop deny-read filesystem policy, so keep the first attempt sandboxed",
|
|
);
|
|
}
|