Skip to content

[RIP-SEC] Windows sandbox: a not-yet-existing protected dir (.codex/.git/.agents) gets no deny rule, so a sandboxed agent can create and poison it #37082

Description

@scadastrangelove

Summary

On Windows, compute_allow_paths_for_permissions gates both the allow and the deny path insertion on
if p.exists(). A protected read-only subpath (.git/.codex/.agents) that does not exist at
session start therefore gets no deny ACE, while its parent writable root is allowed — so a
sandboxed agent can create the directory and write inside it (e.g. .codex/config.toml,
.git/hooks/pre-commit). A later, more-trusted step then consumes it: .codex/config.toml is read as
trusted Codex configuration, .git/hooks/* is executed by git.

Where

codex-rs/windows-sandbox-rs/src/allow.rsadd_deny_path no-ops when the path does not exist:

let mut add_deny_path = |p: PathBuf| { if p.exists() { deny.insert(p); } };

So a missing .codex/.git/.agents under a writable root gets no deny, while the writable-root
allow covers it. Present on current main.

Reproduction

Faithful replication of the function (the if p.exists() gate + the writable-root/read-only-subpath
loop), stand-in read_only_subpaths = [.git, .codex, .agents]:

BEFORE: allow=1 deny=0  -> .codex WRITABLE (bug)   # .codex absent → no deny, cwd allowed
AFTER : deny contains .codex -> control holds       # .codex created → now denied

The gate is purely existence-based. The crate's own test skips_protected_subdirs_when_missing
(windows-sandbox-rs/src/allow.rs) already documents the empty-deny-when-absent behavior.

Impact

On Windows, a sandboxed agent can create and poison a protected metadata directory that did not exist
at session start; the planted config/hook is executed/loaded by a later run. Deferred/conditional on
that later consumption, not immediate RCE.

Note on intent

The empty-deny-when-absent behavior is codified by the existing test above, so it may be considered
by-design; the angle here is the create-then-poison persistence consequence, which is not
documented. Filing at low severity accordingly.

Suggested fix

Emit the deny ACE for every protected read_only_subpath unconditionally (deny a not-yet-existing
path so its creation is refused), or deny the parent's protected-name pattern so first-time creation
is blocked — mirroring the Seatbelt backend, which excludes both the literal path and its subpath.


Found with the rust-in-peace pipeline
(AI-assisted Rust vulnerability research).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingsafety-checkIssues related to safety and abuse checkssandboxIssues related to permissions or sandboxingwindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions