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

Comments

    Image preview