The Sandbox Is a Suggestion: What Black Hat's AI Agent Sandbox Escapes Broke for Operators

An AI agent sandbox escape hit Claude Code Action, Gemini CLI, and Codex at Black Hat 2026. The compensating controls operators can install this week.

AI agent sandbox escape: three arrows leave a dashed vendor sandbox toward the shell, the parent environment, and the next run's instruction file; the operator's control plane sits underneath
The vendor draws the dashed line. You draw the solid one.

Picture Tuesday, 8:15 a.m. A stranger opens an issue on one of your public repos and tags the bot. The resulting GitHub run holds your contents: write token and executes a command the stranger supplied, while the README still says the agent runs sandboxed. Nothing in that scenario requires a jailbreak. The model does what it was told, the harness does what it was built to do, and the badge remains while the token leaves.

That is the shape of an AI agent sandbox escape in 2026, and it is what Novee Security walked through at Black Hat 2026 (Aug 5–6, 2026) across three vendors’ own repositories: Anthropic’s Claude Code Action, Google’s Gemini CLI, and OpenAI’s Codex. The three bugs are patched or patchable. The pattern is not. In each case a trust decision was hardcoded inside the harness where nobody deploying it could see it, and the vendor’s sandbox language covered the gap.

One section here is about what happened. The rest is what you can install without waiting for the next advisory: a least-privilege OS account per agent, secrets that never sit in an environment the agent can read, approval on consequential writes only, and evidence that survives the vendor’s loop. None of it needs a product. Most of it needs an afternoon.

Black Hat 2026, once: three harnesses, one hardcoded trust decision

Novee’s write-up went up on Aug 6, 2026, the day after the talk, under Elad Meged’s byline (Novee Security). Its framing sentence is the one to keep: the findings were made “on the repositories (running default config) of Anthropic, Google, and OpenAI,” and “if you run coding agents in your automations, you are exposed to them too.”

Novee Security’s Black Hat 2026 post header, “If You Run These Automations, You’re Exposed Too,” by Elad Meged, dated August 6, 2026 Screenshot: Novee Security, “Black Hat 2026: If You Run These Automations, You’re Exposed Too” (Aug 6, 2026), captured Sep 13, 2026.

Claude Code Action, CVE-2026-54316. The action’s preprocessor strips single-quoted spans before it scans for injection, since quoted text is inert in a shell. Novee put the payload inside single quotes as a flag value, “then find a program that won’t treat that value as an inert string.” Tag mode’s default tool list “includes git commit and git push,” so the workflow ran with contents: write. Exfiltration went through a public HuggingFace download counter: each request for a model file counts as a download, and the count is a number on a page nobody firewalls (claude-code-action).

Gemini CLI, CVSS 10.0 (GHSA-wpqr-6v78-jr5g). Any GitHub user could open an issue and trigger a headless run. The shell tool’s restriction annotation “is never parsed, stored, or enforced,” so the model got the full run_shell_command, and cat /proc/$PPID/environ reads the parent process because “the child and the parent share the same UID and the same PID namespace, with no unshare, no hidepid.” The upstream repo has “roughly two million monthly installs downstream”; Novee counted “a hundred and fourteen repositories running the same pattern.” The post describes the fix as “a breaking change to how non-interactive headless environments handle folder trust.”

Codex, two passes. OpenAI’s own triage workflow saved the attacker’s issue body into the workspace and ran codex exec twice. A crafted issue makes pass 1’s answer fail schema validation, and “that failure is exactly what launches Pass 2.” Pass 1, diverted, “uses its workspace-write access to write AGENTS.md,” which Codex loads “on every single invocation” as instructions. OpenAI fixed its copy; the section title covers everyone else: “Yours Is Still Running.”

Novee’s key takeaways: remote code execution from a single GitHub issue, exfiltration of live API keys through read-only tools, supply-chain compromise, and persistent agent hijacking via a writable file Screenshot: Novee Security, “Key Takeaways,” Black Hat 2026 post (Aug 6, 2026), captured Sep 13, 2026.

The sentence that ties the three together is about where the decision lived: “a trust classification (isReadOnly) is hardcoded where no one deploying the agent can see it.” Novee’s one operational instruction follows from it: “every file a workflow writes should be treated as an untrusted input surface, and so should every workflow.”

The news stops here. The rest is what you do about a harness you did not write and cannot see into.

Why “sandboxed” stopped being a control once agents started acting

A sandbox is a boundary around the model’s tool calls. All three escapes crossed a boundary the sandbox was not drawn around: the workflow string that became a shell command, the parent process that held the real secrets, the instruction file the next run would read as gospel. The model stayed inside the box. The box was in the wrong place.

That is why securing AI agents starts with least privilege rather than a sandbox flag. A chatbot with a leaked key can run up a bill; an agent holding a runner’s token can push a commit with your bot’s name on it. Once the model acts, the question stops being “is the model contained” and becomes “what can this process reach, and who decided that.” If the answer to the second half is “a constant in the vendor’s source,” give the agent the scrutiny you would give any privileged user on the payroll.

Compensating controls for an AI agent sandbox escape, in install order

Budget an afternoon for the first agent and an hour per agent after that. The order matters, because each step assumes the one before it holds.

Stat tiles: CVSS 10.0 for the Gemini CLI headless chain, roughly two million monthly installs downstream, 114 repositories running the same pattern, three vendors with one shape Real numbers, Novee’s count, not modeled: one hardcoded trust decision at scale.

Step 1: Draw the boundary map for every headless agent you run

Take each unattended agent and answer three questions on paper: what text can a stranger get into this run, which process holds the credentials while it executes, and which file written during this run will a later run read as instructions. Those are the three escapes, and the diagram shows the control that sits on each.

Diagram of where each Black Hat 2026 escape crossed a boundary and where the compensating control sits Three boundaries the vendor sandbox did not cover, and the control that covers each.

Step 2: One least-privilege OS account per agent, and a wall the child cannot see through

The Gemini CLI chain worked because the model’s shell ran as the same user as the harness holding the secrets. The fix is older than agents: two users. The harness runs as one account; the shell tool runs as another with no view of the first’s process tree. On Linux, mounting /proc with hidepid=invisible hides other users’ processes, so /proc/$PPID/environ has nothing to give across the UID line (kernel.org).

# Illustrative shapes, not a vendor template. Adapt to your init and container runtime.
sudo useradd --system --create-home --shell /usr/sbin/nologin agent-codex   # no sudoers entry, ever
sudo mount -o remount,hidepid=invisible /proc                              # other UIDs' /proc vanishes
sudo -u agent-codex env -i PATH="/usr/local/bin:/usr/bin:/bin" HOME=/home/agent-codex \
  codex exec --sandbox read-only "summarize the repository and list the top 5 risky areas"

The env -i is the point: the agent’s process starts empty and gets only what you name. In a container the same shape is a non-root USER, --cap-drop=ALL, a read-only root filesystem, and no Docker socket mounted. If the harness runs tools as itself, put the whole harness under the throwaway user and move the credentials out of it, which is Step 3.

Step 3: Keep every key out of anything the agent can read

The environment is not a secret store. Child processes inherit it unless the launcher scrubs it, same-UID processes may read it through /proc, and crash handling can expose it. The Codex non-interactive docs make the rule concrete: pass CODEX_API_KEY inline for a single invocation, never as a job-level variable in a workflow that checks out untrusted code (Codex non-interactive mode).

Do the same for everything else. Cloud credentials come from OIDC in the job that needs them, not from a secret pasted into the agent’s step. Tool tokens are minted at call time by a broker or a credential helper and expire in minutes; the model holds a reference, never a value. That pattern, with the identity to go with it, is the whole of agents as service principals.

Then audit it on every run, before the prompt. The first thing the agent’s shell does is env | cut -d= -f1 | sort | diff allowed-env-names.txt -, and any difference fails the run. Names only, never values, and the allowlist is a file in the repo that a reviewer owns. An unexpected name is a failed run, not a warning. I have never seen an env allowlist that was too short.

Step 4: Put approval on consequential writes, not on reads

Approval prompts in an unattended run are either a hang or an auto-deny, so the gate cannot be a dialog. Split the run instead. The agent job reads and proposes: codex exec defaults to a read-only sandbox, Claude Code’s plan mode explores without editing, and its dontAsk mode auto-denies anything that would have prompted (Codex non-interactive mode; Claude Code permissions). The output is a patch uploaded as an artifact. A second job, which never receives the model API key, applies the patch under contents: write inside a GitHub environment with required reviewers (GitHub Docs).

Write class Examples Gate
Reversible, in-branch edits on a throwaway branch, a draft PR none; the patch artifact is the record
Consequential, in-repo push to a protected branch, merge, release tag environment with a required reviewer; CODEOWNERS on workflows, instruction files, lockfiles
Consequential, outside the repo deploy, package publish, a customer-facing message never from the agent job; a human-approved job with its own identity

The Claude Code Action finding is the argument for the middle row: tag mode’s defaults included git push, so contents: write reached a step a stranger’s issue could steer. A workflow-wide permissions: contents: read, with write granted only in the apply job, turns the same injection into a nuisance.

Step 5: Treat every file a workflow wrote as attacker input

Codex loads AGENTS.md on every invocation and reads .rules and $CODEX_HOME/config.toml. Claude Code reads CLAUDE.md, project settings, hooks, and .mcp.json. Git reads .git/config before any of them. Each is a file, and in an automation a previous pass, a contributor’s PR, or an unpacked archive can write it. GitSpawn made the .git/config case its own intake problem (Manifold Security). Four moves, in order of effort:

  1. A fresh checkout per pass. If a workflow runs the agent twice, the second run gets a new clone, or git clean -fdx && git checkout -- . from the base commit. Pass 1 can then leave nothing behind for pass 2.
  2. Ignore repo-supplied config in headless runs. For Codex, --ignore-rules skips user and project .rules files and --ignore-user-config skips $CODEX_HOME/config.toml. For claude -p, pass --setting-sources user or start with --bare, and add --settings '{"disableAllHooks": true}'; the docs note that a -p session counts the folder as trusted, so .mcp.json servers connect without asking unless you say otherwise (Claude Code permissions).
  3. Pin instruction files by hash. Record sha256sum AGENTS.md CLAUDE.md .rules at the start and the end of the run. A difference fails the run and pages a human.
  4. Protect them in review. CODEOWNERS on the instruction files and the workflow directory, with a required reviewer who is not the bot.

Step 6: Keep evidence that survives the vendor loop

The runner is torn down when the job ends, and the vendor’s UI keeps a transcript for as long as the vendor decides. Neither is yours. Export, per run, before teardown: the event stream (--json for Codex, the JSON output for Claude Code), the final message (-o), the patch, the env-name manifest from Step 3, the instruction-file hashes from Step 5, the egress log if you have one, and the input’s provenance (issue author and repo association). Upload it as an artifact, then copy it to a store you control, because artifacts rotate too. Fleet replay is what makes those files answer questions six weeks later.

One more rule, learned from the download-counter exfil: if a secret was present in the environment of a vulnerable run, rotate it, whether or not you found a leak. Zero-click channels do not appear in egress logs.

Tuesday checklist Shape Signal it is working
Separate OS user, /proc walled off sudo -u agent-<name> env -i …; hidepid=invisible cat /proc/$PPID/environ from the tool user is denied
Env allowlist diff allowed-env-names.txt <(env | cut -d= -f1 | sort) zero unexpected names on every run
Read-only agent job codex exec --sandbox read-only / claude -p --permission-mode plan the token is contents: read and the run still finishes
Writes in a gated job environment with required reviewers every bot merge shows a human approval on the apply job
Instruction files pinned sha256sum before and after a mismatch fails the run

Five signals that a compensating control is theater

  1. The job hits its timeout with no final message. A prompt was waiting for a human who does not exist. Pin the approval flag.
  2. The agent “finished” and the event stream shows denied tool calls. In an unattended tier a denied call is a policy gap or an attempt. Zero is the target; any other number gets a human.
  3. The instruction-file hash changed during a run. That is the Codex pattern in your house. Quarantine the branch and read the diff before anything the agent said.
  4. An env-name diff shows an extra variable. Someone added a job-level secret to save time; a child process can now inherit it.
  5. The same wrapper runs in three repos with three different flag lines. One is at the wrong tier. Diff the wrappers weekly; the headless trust tier gives you the flag line per tier.

The sandbox is the vendor’s feature; the control plane is yours

A sandbox badge tells you where the vendor drew a line. It does not tell you where the token sits, which user the shell runs as, or which file the next run trusts. Those are operating decisions, and they belong in a layer you own: the OS account, the broker, the gated write job, the evidence store.

That is the fleet argument in one sentence: agents act, so the desk needs an operating layer rather than a smarter prompt. A restricted-mode fleet policy is the same idea applied to permission modes, and a multi-agent command center is where “which account did run 4182 execute as” becomes a lookup instead of an afternoon. The three Black Hat findings were fixed in three codebases you do not maintain. The controls above hold in the one you do.

FAQ: AI agent sandbox escape

How do I stop an AI agent from reading environment variables?

Start it with an empty environment (env -i) and only the names you allow, run its shell tool as a different OS user from the process holding credentials, mount /proc with hidepid, and pass API keys inline per invocation rather than as job-level variables. Then diff the env names on every run.

Should Gemini CLI still run in CI after the CVSS 10.0 finding?

Google stopped serving Gemini CLI to individual, AI Pro, and Ultra accounts on June 18, 2026, while enterprise and API-key access remained (Google Developers). Remove the vulnerable headless pattern from workflows strangers can trigger, rotate every secret those runs could see, and require the unattended trust tier before restoring it.

Sources