feat: if .codex is a sub-folder of a writable root, then make it read-only to the sandbox (#8088)

In preparation for in-repo configuration support, this updates
`WritableRoot::get_writable_roots_with_cwd()` to include the `.codex`
subfolder in `WritableRoot.read_only_subpaths`, if it exists, as we
already do for `.git`.

As noted, currently, like `.git`, `.codex` will only be read-only under
macOS Seatbelt, but we plan to bring support to other OSes, as well.

Updated the integration test in `seatbelt.rs` so that it actually
attempts to run the generated Seatbelt commands, verifying that:

- trying to write to `.codex/config.toml` in a writable root fails
- trying to write to `.git/hooks/pre-commit` in a writable root fails
- trying to write to the writable root containing the `.codex` and
`.git` subfolders succeeds
This commit is contained in:
Michael Bolin
2025-12-15 22:54:43 -08:00
committed by GitHub
parent f074e5706b
commit bef36f4ae7
3 changed files with 228 additions and 66 deletions

View File

@@ -306,12 +306,14 @@ pub enum SandboxPolicy {
/// A writable root path accompanied by a list of subpaths that should remain
/// readonly even when the root is writable. This is primarily used to ensure
/// toplevel VCS metadata directories (e.g. `.git`) under a writable root are
/// not modified by the agent.
/// that folders containing files that could be modified to escalate the
/// privileges of the agent (e.g. `.codex`, `.git`, notably `.git/hooks`) under
/// a writable root are not modified by the agent.
#[derive(Debug, Clone, PartialEq, Eq, JsonSchema)]
pub struct WritableRoot {
pub root: AbsolutePathBuf,
/// By construction, these subpaths are all under `root`.
pub read_only_subpaths: Vec<AbsolutePathBuf>,
}
@@ -458,6 +460,13 @@ impl SandboxPolicy {
if top_level_git.as_path().is_dir() {
subpaths.push(top_level_git);
}
#[allow(clippy::expect_used)]
let top_level_codex = writable_root
.join(".codex")
.expect(".codex is a valid relative path");
if top_level_codex.as_path().is_dir() {
subpaths.push(top_level_codex);
}
WritableRoot {
root: writable_root,
read_only_subpaths: subpaths,