mirror of
https://github.com/openai/codex.git
synced 2026-11-03 08:22:30 +00:00
feat(config): support managed deny-read requirements (#17740)
## Summary
- adds managed requirements support for deny-read filesystem entries
- constrains config layers so managed deny-read requirements cannot be
widened by user-controlled config
- surfaces managed deny-read requirements through debug/config plumbing
This PR lets managed requirements inject deny-read filesystem
constraints into the effective filesystem sandbox policy.
User-controlled config can still choose the surrounding permission
profile, but it cannot remove or weaken the managed deny-read entries.
## Managed deny-read shape
A managed requirements file can declare exact paths and glob patterns
under `[permissions.filesystem]`:
```toml
# /etc/codex/requirements.toml
[permissions.filesystem]
deny_read = [
"/Users/alice/.gitconfig",
"/Users/alice/.ssh",
"./managed-private/**/*.env",
]
```
Those entries are compiled into the effective filesystem policy as
`access = none` rules, equivalent in shape to filesystem permission
entries like:
```toml
[permissions.workspace.filesystem]
"/Users/alice/.gitconfig" = "none"
"/Users/alice/.ssh" = "none"
"/absolute/path/to/managed-private/**/*.env" = "none"
```
The important difference is that the managed entries come from
requirements, so lower-precedence user config cannot remove them or make
those paths readable again.
Relative managed `deny_read` entries are resolved relative to the
directory containing the managed requirements file. Glob entries keep
their glob suffix after the non-glob prefix is normalized.
## Runtime behavior
- Managed `deny_read` entries are appended to the effective
`FileSystemSandboxPolicy` after the selected permission profile is
resolved.
- Exact paths become `FileSystemPath::Path { access: None }`; glob
patterns become `FileSystemPath::GlobPattern { access: None }`.
- When managed deny-read entries are present, `sandbox_mode` is
constrained to `read-only` or `workspace-write`; `danger-full-access`
and `external-sandbox` cannot silently bypass the managed read-deny
policy.
- On Windows, the managed deny-read policy is enforced for direct file
tools, but shell subprocess reads are not sandboxed yet, so startup
emits a warning for that platform.
- `/debug-config` shows the effective managed requirement as
`permissions.filesystem.deny_read` with its source.
## Stack
1. #15979 - glob deny-read policy/config/direct-tool support
2. #18096 - macOS and Linux sandbox enforcement
3. This PR - managed deny-read requirements
---------
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
1 parent
2dd6734dd3
commit
dae0608c06
10 files changed
+509
-1
No files matched your search
@@ -5306,6 +5306,7 @@ async fn test_requirements_web_search_mode_allowlist_does_not_warn_when_unset()
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
permissions: None,
|
||||
guardian_policy_config: None,
|
||||
};
|
||||
let requirement_source = crate::config_loader::RequirementSource::Unknown;
|
||||
@@ -5949,6 +5950,7 @@ async fn explicit_sandbox_mode_falls_back_when_disallowed_by_requirements() -> s
|
||||
rules: None,
|
||||
enforce_residency: None,
|
||||
network: None,
|
||||
permissions: None,
|
||||
guardian_policy_config: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -1250,6 +1250,37 @@ fn resolve_permission_config_syntax(
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_managed_filesystem_constraints(
|
||||
file_system_sandbox_policy: &mut FileSystemSandboxPolicy,
|
||||
filesystem_constraints: &crate::config_loader::FilesystemConstraints,
|
||||
) {
|
||||
for deny_read in &filesystem_constraints.deny_read {
|
||||
let deny_entry = if deny_read.contains_glob() {
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::GlobPattern {
|
||||
pattern: deny_read.as_str().to_string(),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::None,
|
||||
}
|
||||
} else {
|
||||
let Ok(path) = AbsolutePathBuf::try_from(deny_read.as_str()) else {
|
||||
continue;
|
||||
};
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Path { path },
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::None,
|
||||
}
|
||||
};
|
||||
if !file_system_sandbox_policy
|
||||
.entries
|
||||
.iter()
|
||||
.any(|existing| existing == &deny_entry)
|
||||
{
|
||||
file_system_sandbox_policy.entries.push(deny_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional overrides for user configuration (e.g., from CLI flags).
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ConfigOverrides {
|
||||
@@ -1461,6 +1492,7 @@ impl Config {
|
||||
exec_policy: _,
|
||||
enforce_residency,
|
||||
network: network_requirements,
|
||||
filesystem: filesystem_requirements,
|
||||
} = config_layer_stack.requirements().clone();
|
||||
|
||||
let user_instructions = AgentsMdManager::load_global_instructions(Some(&codex_home))
|
||||
@@ -1970,6 +2002,34 @@ impl Config {
|
||||
&mut constrained_approval_policy,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
if let Some(Sourced {
|
||||
value: filesystem_requirements,
|
||||
source: filesystem_requirements_source,
|
||||
}) = filesystem_requirements.as_ref()
|
||||
&& !filesystem_requirements.deny_read.is_empty()
|
||||
{
|
||||
let requirement_source = filesystem_requirements_source.clone();
|
||||
constrained_sandbox_policy
|
||||
.value
|
||||
.add_validator(move |policy| match policy {
|
||||
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. } => Ok(()),
|
||||
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
|
||||
Err(ConstraintError::InvalidValue {
|
||||
field_name: "sandbox_mode",
|
||||
candidate: policy.to_string(),
|
||||
allowed: "[read-only, workspace-write]".to_string(),
|
||||
requirement_source: requirement_source.clone(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.map_err(std::io::Error::from)?;
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
startup_warnings.push(format!(
|
||||
"managed filesystem deny_read from {filesystem_requirements_source} is only enforced for direct file tools on Windows; shell subprocess reads are not sandboxed"
|
||||
));
|
||||
}
|
||||
}
|
||||
apply_requirement_constrained_value(
|
||||
"approvals_reviewer",
|
||||
approvals_reviewer,
|
||||
@@ -2023,7 +2083,7 @@ impl Config {
|
||||
main_execve_wrapper_exe.as_ref(),
|
||||
);
|
||||
let effective_sandbox_policy = constrained_sandbox_policy.value.get().clone();
|
||||
let effective_file_system_sandbox_policy =
|
||||
let mut effective_file_system_sandbox_policy =
|
||||
if effective_sandbox_policy == original_sandbox_policy {
|
||||
file_system_sandbox_policy
|
||||
} else {
|
||||
@@ -2033,6 +2093,16 @@ impl Config {
|
||||
&file_system_sandbox_policy,
|
||||
)
|
||||
};
|
||||
if let Some(Sourced {
|
||||
value: filesystem_requirements,
|
||||
..
|
||||
}) = filesystem_requirements.as_ref()
|
||||
{
|
||||
apply_managed_filesystem_constraints(
|
||||
&mut effective_file_system_sandbox_policy,
|
||||
filesystem_requirements,
|
||||
);
|
||||
}
|
||||
let effective_file_system_sandbox_policy = effective_file_system_sandbox_policy
|
||||
.with_additional_readable_roots(resolved_cwd.as_path(), &helper_readable_roots);
|
||||
let effective_network_sandbox_policy =
|
||||
|
||||
Reference in new issue
Block a user