mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
Add executor-context filesystem permission helpers (#43939)
## Why Filesystem policies for remote execution need to interpret paths, home directories, and temporary directories using the execution host's context. ## What changed - Add context-based read-denial matching, read-access checks, glob resolution, and `PathUri` workspace-write constructors without consulting local paths or environment variables. - Share managed read-denial validation through `DenyReadValidator` and use it in core configuration, preserving required-entry checks, concrete grant validation, and constraint diagnostics. - Reject invalid or unresolvable denial paths when constructing a context-based matcher, and apply `/tmp` denials according to the execution host's path convention. - Preserve workspace-root symbols and protected metadata entries when materializing URI-based writable roots. ## Testing Add regression tests for POSIX, Windows, and UNC path matching; conflicting read and write grants; malformed or unresolvable denials; `/tmp` handling; and workspace metadata protection. GitOrigin-RevId: aa2b174288d1172ab52ff485f58d306236d27652
This commit is contained in:
@@ -116,10 +116,12 @@ use codex_protocol::models::SandboxEnforcement;
|
||||
use codex_protocol::openai_models::ModelMessages;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::permissions::DenyReadValidator;
|
||||
use codex_protocol::permissions::DenyReadViolation;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicyContext;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::permissions::ReadDenyMatcher;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::MultiAgentVersion;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
@@ -4101,49 +4103,65 @@ impl Config {
|
||||
}) = filesystem_requirements.as_ref()
|
||||
&& let Some(managed_file_system_policy) = managed_deny_read_policy.as_ref()
|
||||
{
|
||||
let managed_deny_matcher =
|
||||
ReadDenyMatcher::try_new_for_local_paths(managed_file_system_policy, resolved_cwd.as_path())
|
||||
.map_err(std::io::Error::other)?;
|
||||
let managed_file_system_policy = Arc::clone(managed_file_system_policy);
|
||||
let cwd = PathUri::from_abs_path(&resolved_cwd);
|
||||
let user_home_dir = PathUri::from_host_native_path("~").ok();
|
||||
let temporary_directories = std::env::var_os("TMPDIR")
|
||||
.filter(|path| !path.is_empty())
|
||||
.and_then(|path| AbsolutePathBuf::from_absolute_path(PathBuf::from(path)).ok())
|
||||
.map(PathUri::from)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
let context = FileSystemSandboxPolicyContext {
|
||||
cwd: &cwd,
|
||||
workspace_roots: std::slice::from_ref(&cwd),
|
||||
user_home_dir: user_home_dir.as_ref(),
|
||||
temporary_directories: Some(&temporary_directories),
|
||||
};
|
||||
let validator = DenyReadValidator::new(managed_file_system_policy, &context)
|
||||
.map_err(std::io::Error::other)?;
|
||||
let requirement_source = requirement_source.clone();
|
||||
constrained_permission_profile
|
||||
.value
|
||||
.add_validator(move |permission_profile| {
|
||||
let file_system_policy = permission_profile.file_system_sandbox_policy();
|
||||
let missing_required_deny = managed_file_system_policy
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| !file_system_policy.entries.contains(entry));
|
||||
let violating_root = file_system_policy
|
||||
.entries
|
||||
.iter()
|
||||
.filter(|entry| entry.access.can_read())
|
||||
.find_map(|entry| {
|
||||
let FileSystemPath::Path { path } = &entry.path else {
|
||||
return None;
|
||||
let mut file_system_policy = permission_profile.file_system_sandbox_policy();
|
||||
// Preserve the native conversion boundary before the shared URI checks.
|
||||
// Mandatory entries are Deny entries, so their identity stays unchanged.
|
||||
file_system_policy.entries.retain_mut(|entry| {
|
||||
if entry.access.can_read()
|
||||
&& let FileSystemPath::Path { path } = &mut entry.path
|
||||
{
|
||||
let Ok(native_path) = path.to_abs_path() else {
|
||||
return false;
|
||||
};
|
||||
let path = path.to_abs_path().ok()?;
|
||||
managed_deny_matcher
|
||||
.as_ref()
|
||||
.is_some_and(|matcher| matcher.is_local_path_read_denied(path.as_path()))
|
||||
.then_some(path)
|
||||
});
|
||||
if missing_required_deny || violating_root.is_some() {
|
||||
return Err(ConstraintError::InvalidValue {
|
||||
*path = PathUri::from(native_path);
|
||||
}
|
||||
true
|
||||
});
|
||||
let context = FileSystemSandboxPolicyContext {
|
||||
cwd: &cwd,
|
||||
workspace_roots: std::slice::from_ref(&cwd),
|
||||
user_home_dir: user_home_dir.as_ref(),
|
||||
temporary_directories: Some(&temporary_directories),
|
||||
};
|
||||
validator.validate(&file_system_policy, &context).map_err(|violation| {
|
||||
let candidate = match violation {
|
||||
DenyReadViolation::MissingRequiredDeny => "missing managed deny".to_string(),
|
||||
DenyReadViolation::ReadablePath(path) => path.to_abs_path().map_or_else(
|
||||
|_| path.to_string(),
|
||||
|path| path.to_string_lossy().into_owned(),
|
||||
),
|
||||
};
|
||||
ConstraintError::InvalidValue {
|
||||
field_name: "permissions.filesystem",
|
||||
candidate: violating_root
|
||||
.map_or_else(|| "missing managed deny".to_string(), |path| {
|
||||
path.to_string_lossy().into_owned()
|
||||
}),
|
||||
candidate,
|
||||
allowed: "all managed deny_read restrictions".to_string(),
|
||||
requirement_source: requirement_source.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
})
|
||||
.map_err(std::io::Error::from)?;
|
||||
}
|
||||
|
||||
let permission_profile_state = PermissionProfileState::from_constrained_active_profile(
|
||||
constrained_permission_profile.value,
|
||||
active_permission_profile,
|
||||
|
||||
Reference in New Issue
Block a user