Letting an AI Break My Gate
The gate bypasses Codex found and the checks added to block each path
I gave Codex the complete Claude-authored gate and asked it to find ways a main session could bypass it. None of the returned paths appeared in my existing tests.
The bypasses spanned five places: the parser, the exemption condition, the kill switch, the gate itself, and the counter.
1. Shifting Parsed Fields with a Newline
As seen in part 01, the gate parses
the tool-call JSON into a single tab-joined line and splits the fields with cut. If a newline is
inside the command, that newline survives into the parsed result too. The fields that follow get
pushed onto the next line, a wrong value lands in the agent_id slot, and the subagent exemption
can fire when it should not.
The Bash gate blocks this in two layers. While parsing, it converts newlines inside the command
into the SOH character (\x01) to make one line, then converts them back to newlines after the
fields are split. And if a real newline is still present in the parsed result, it treats that as
tampering and blocks.
case "$_parsed" in
*$'\n'*)
printf 'orchestration gate: unexpected multiline parser output; failing closed.\n' >&2
exit 2
;;
esac
The file gate got only this guard, without the encoding. A newline arriving in a file path is not
as common as one in a command, but if it does arrive the fields shift the same way. The case
block above appears in both scripts with the same text.
2. Stacking a Command onto the codex Prefix
The Bash gate exempts commands that start with codex, because the delegation path always has to
work. The first version looked only at the prefix. As long as it started with
codex, it passed no matter what was attached after it.
codex --version; printf x > /tmp/pwn
It starts with codex --version, but a file write is attached after the semicolon. A
prefix-only exemption lets this straight through.
I narrowed the exemption condition. It exempts only when the command starts with codex, has
no command substitution ($(...), backticks), and what remains after stripping quotes leaves only
safe characters. A compound command with a semicolon or a redirect attached drops out of the
exemption and falls through to the redirect check that follows.
3. Creating the Kill Switch File with a Command
The gate turns itself off when the ~/.claude/orch-gate-off file exists. It is a switch meant for
a human to flip on and off. But if the main session creates that file with a command, it can turn
the gate off with its own hands.
touch .claude/orch-gate-off
This command is neither a redirect nor tee, so it does not trip the file-write pattern check. So
I put a separate check ahead of it. If the command contains the string orch-gate-off together
with a file-creating token such as touch, mkdir, ln, or truncate, it is blocked.
Conversely, read commands that only mention the name without creating the file (cat, grep) are
let through.
Codex also found that quotes could evade the raw string check.
touch .claude/orch-gate-o''ff
When the shell runs orch-gate-o''ff, the empty quotes disappear and it becomes orch-gate-off.
Searching the raw string as-is misses this. So the sentinel is checked against both copies: the raw
string and the string with quotes stripped. Stripping the quotes reassembles orch-gate-off, so it
is caught there.
4. Editing the Gate Itself
Editing the gate script or its settings outright can also bypass the limit. You make the file
gate perform the edit that disables itself. Subagents are exempt, so this check has to sit after
the subagent exemption and before the code extension filter. The script is a .sh and so is caught
as code, but the settings file is a .json and passes the extension filter unchallenged.
case "$_target_path" in
*'.claude/hooks/'*|*'.claude/settings.json'|*'.claude/settings.local.json'|*'orch-gate-off'*)
printf 'Main-session write to orchestration gate infrastructure blocked...\n' >&2
exit 2
;;
esac
Comparing the path by name alone can be evaded by inserting slashes, as in .claude//hooks or
./.claude/./hooks. So before comparing, /./ and consecutive slashes are folded down to one.
Parent-directory traversal with .. mixed in is hard to resolve exactly in a POSIX shell, so when
.. and .claude appear together it is treated as tampering and blocked.
On the Bash gate side, the same goal was aimed at differently. The method is to use git restore
to revert the parent directory’s .claude and roll the gate back to an earlier state. If git is
invoked with a file-writing subcommand (restore, checkout, reset, and so on) and .claude is
mixed into the command, it is blocked. Conversely, git grep and git log are read-only and pass.
5. Not Counting When the Lock Fails
The edit counter takes the lock and increments the value. The first version simply proceeded when it could not take the lock or could not write the value. It let the edit through without counting it. That means the counter can be pinned at zero just by deliberately blocking the lock.
I changed the gate to block when it cannot take the lock or write the counter.
if ! printf '%s' "$_count" > "$counter_file"; then
printf 'orchestration gate: could not persist edit counter; failing closed.\n' >&2
exit 2
fi
Failing to take the lock or to write the counter file blocks. It is the same treatment as when there is no parser.
Limits That Remained After the Bypass Tests
The Bash gate received checks for parser tampering, the Codex exemption, and kill-switch creation. The file gate received infrastructure protection, fail-closed counter handling, and its own newline guard. Most bypasses were shell commands. These checks reduce accidental and straightforward bypasses, but the Bash gate remains best-effort rather than a security boundary.
The bypass of saving under a changed extension is still open today. The first comment in the script says this.
BEST-EFFORT ANTI-DRIFT GATE. Renaming a code file to an excluded extension
(e.g. .json) bypasses the extension filter.
Every place I fixed became a regression test. For each bypass string I added a “this blocks” case,
and for each command that resembles that string but is legitimate a paired “this passes” case,
which came to 42. It checks whether git grep 'restore' passes and git restore is blocked, and
even whether a command that merely mentions the sentinel passes. Running
tests/gate-tests.sh, included in the
gate bundle, reproduces all of it.
In this test, Codex found bypasses that were absent from the tests written with the Claude-authored gate. Applying that result to ordinary code review required a separate rule for assigning authors and reviewers.
References
Comments
No comments yet. Be the first to leave one.
Pending review