Blog

min read

The Week of Sandbox Escapes: GitPwned: Allowlist to RCE

By

Ariel Fogel

and

July 20, 2026

min read

Codex CLI's second sandbox escape—and why model behavior isn't a security control

TL;DR

This post is part of The Week of Sandbox Escapes, a series on how AI coding agents keep crossing the line between sandboxed workspace actions and unsandboxed host execution. Read the master report here.

OpenAI's Codex CLI trusts git show as a safe command, but git show --output can write arbitrary content to arbitrary files—including .git/config. By injecting a malicious external diff tool into the config, an attacker achieves remote code execution the next time the user runs git diff. The attack requires no user approval and bypasses the sandbox entirely.

This is the second sandbox escape (CVE GHSA-w5fx-fh39-j5rw) in Codex CLI since September. The mechanisms differ, but both involve deterministic security controls that didn't account for the full range of model-generated input.

You're integrating a new package. You ask Codex for help. It reads the README, and somewhere between the installation instructions and the API examples is a line the model decides to follow. A git command that runs with your full privileges, because nothing in the security model thinks to stop it.

Later, you run git diff to review your changes. The payload executes.

The command that made this possible—git show—is on Codex CLI's safe command allowlist, which ships enabled by default. Commands on this list bypass the sandbox, skip user approval, and execute with whatever privileges you have. The theory is that these commands can't cause harm regardless of their arguments. The theory is wrong: git show --output can write to .git/config, and when paired with --format, an attacker controls what gets written.

The allowlist gap is the mechanism. The deeper issue is that deterministic security controls—the sandbox, the approval flow—defer entirely to the allowlist. When the allowlist has a gap, nothing else catches it.

We reported this to OpenAI's security team on January 7. The vulnerability has been patched in version v0.95.0.

The Vulnerability: Arbitrary File Write via git show --output

Codex CLI ships with a safe command allowlist enabled by default. Commands on this list bypass both sandbox restrictions and user approval, on the assumption that they can't cause harm regardless of arguments. git show is on this list because it's supposed to just display information about commits.

It can also write arbitrary content to arbitrary paths.

Git's show command accepts an --output flag that redirects output to a file instead of stdout. Combined with the --format flag, which controls what content is written, an attacker can craft a command that writes whatever they want wherever they want. The attack looks like this:

git show --format='[diff]%nexternal = bash -c '"'"'open -a Calculator'"'"'' \
    --no-patch --output=./.git/config HEAD

Each piece serves a purpose. git show gets the command past the allowlist. --format='[diff]%n...' crafts valid git config syntax, with %n producing a newline. external = bash -c '...' tells git to use a custom diff tool—one that happens to be the attacker's payload. --no-patch suppresses normal output so only the format string is written. --output=./.git/config writes directly to the repository's config file. HEAD is any valid commit reference.

After this command runs, .git/config contains:

[diff]
    external = bash -c 'open -a Calculator'

The next time anyone runs git diff—also on the safe allowlist—git invokes the external diff tool. The attacker's payload executes with full user privileges.

A Gap in the Allowlist Is a Gap in Everything

This isn't a case where one check fails and another catches the problem. The entire security model defers to the allowlist.

The safe command allowlist in is_safe_command.rs explicitly trusts git show:

Some("git") => matches!(
    command.get(1).map(String::as_str),
    Some("branch" | "status" | "log" | "diff" | "show")
),

Because the command matches this pattern, it skips approval entirely. The sandbox never sees it. The dangerous command detection only looks for git reset and git rm, not git show. There are no shell metacharacters involved, so shell parsing provides no protection—git handles --output internally.

The result is that attacker-controlled content lands in .git/config, and every security layer watches it happen.

Model Refusal Is Not a Security Control

When we first submitted this report, Bugcrowd's triage team couldn't reproduce it—the model refused to follow the injected instruction. We had to explain why that didn't matter.

Model refusal is probabilistic, not deterministic. Run the same prompt ten times and you'll get different results. The model might refuse nine times and comply on the tenth. For security analysis, the question isn't whether the model refuses on any given attempt—it's what happens when it complies. In Codex CLI, when the model complies with an instruction to run git show --output, nothing stops the attack. The architecture assumes the model will refuse, and when it doesn't, there's no fallback.

If you're processing untrusted content through a language model, you have to design for the case where the model follows malicious instructions. The security architecture has to ensure that when this happens, the damage is contained.

Demonstration

The video below shows the full attack chain. A user asks Codex to help with changelog generation and points it to external documentation. That documentation contains the injected payload.

The contrast is worth watching for: when Codex attempts to run curl, it requires explicit user approval, because curl isn't on the safe allowlist and the sandbox is working as intended. When it runs git show --output, nothing happens—no prompt, no intervention. The command executes silently, the config file is overwritten, and subsequent git operations trigger the payload.

Both commands have security implications. Only one is controlled.

Impact

We demonstrated arbitrary code execution by launching Calculator.app on macOS. The payload triggers when the user runs git diff—which, for most developers working in a repository, happens routinely.

The attack writes specifically to .git/config, and the payload executes with whatever privileges the user has when they invoke git. What an attacker does with code execution is up to them.

Mitigations

If you use Codex CLI: Update to version v0.95.0 or later, which patches this vulnerability.

If you're building AI-powered developer tools: Command-name allowlists don't work for commands with complex option semantics. git, curl, tar, rsync—any tool with flags that change behavior from read-only to write-capable can't be safely allowlisted by name alone.

Consider these alternatives:

Block dangerous flags explicitly. Check for --output, --format, and similar flags that change a command's behavior:

Some("git") => {
    let subcommand = command.get(1).map(String::as_str);
    let has_dangerous_flag = command.iter().any(|arg| {
        arg.starts_with("--output") || arg.starts_with("-o") ||
        arg.starts_with("--format") || arg.starts_with("--pretty")
    });
    matches!(subcommand, Some("branch" | "status" | "log" | "diff" | "show"))
        && !has_dangerous_flag
}

This is better than trusting command names, but git has a lot of flags, and you'd need to audit each one.

Remove git from the safe allowlist entirely. Requiring approval for all git operations adds friction, but it closes the gap.

Override dangerous config at runtime. Running git -c diff.external= diff ensures no external diff tool is invoked regardless of what's in the config file. This doesn't prevent the file write, but it breaks this particular execution chain.

Disclosure Timeline

  • Jan 7, 2026: Initial report submitted via Bugcrowd
  • Jan 8, 2026: Bugcrowd requests clarification on reproduction; we provided video demonstrating successful exploitation
  • Jan 15, 2026: Bugcrowd escalates to OpenAI for review
  • Jan 22, 2026: OpenAI triages as P2, confirms fix in progress
  • Jan 29, 2026: OpenAI confirms CVSS 8.6, consistent with the prior sandbox escape GHSA-w5fx-fh39-j5rw
  • February 4, 2026: Patch released in version v0.95.0
  • February 17, 2026: Bounty awarded
  • July 20, 2026: Public disclosure (this post)

Conclusion

This is the second sandbox escape in Codex CLI since September. The first exploited the sandbox configuration logic: the CLI used a model-generated working directory to define its own security boundaries, letting the model specify where it was allowed to write. This one exploits the safe command allowlist, which trusts command names without examining arguments. The mechanisms are different, but both involve deterministic security controls that didn't account for the full range of model-generated input.

Trying to ensure a model never follows a malicious instruction is a difficult problem to solve at the model layer—the search space is vast, and the line between legitimate and malicious instructions is often contextual. The more productive approach is to build trust boundaries that reflect the actual risk. In AI coding agents, the potential for impact is high: these tools run with developer privileges, process untrusted content by design, and execute code. The deterministic systems around the model—sandboxes, allowlists, approval flows—should be designed to minimize the damage when the model does something unexpected, not to assume it won't.

Commands aren't inherently safe or dangerous; invocations are. git show HEAD is harmless. git show --output=.git/config HEAD can write attacker-controlled content to a sensitive file. An allowlist that sees only the command name can't tell the difference.

Subscribe and get the latest security updates

Back to blog

MAYBE YOU WILL FIND THIS INTERSTING AS WELL

The Week of Sandbox Escapes: Git directories do not have to be called .git

By

Eilon Cohen

and

July 20, 2026

Research
The Week of Sandbox Escapes: The sandbox let me edit a venv, and something else ran it

By

Dan Lisichkin

and

July 20, 2026

Research
The Week of Sandbox Escapes: A time bomb in .vscode: bypassing Antigravity's Secure Mode

By

Dan Lisichkin

and

July 20, 2026

Research