Two Harnesses, One Floor
I found the worst security hole in my setup written, in plain English, inside an instruction file I had authored to make an agent safer.
It was a single line in AGENTS.override.md, the operating instructions for my Codex harness, the second
coding agent I run on the same machine as Claude Code. The line was advice about git, and it read, near enough
to verbatim:
Prefer tracked-branchgit pushfor branches that already track a remote, especiallymain, because this avoids command-form guardrails.
I had a guard: a hook that blocks pushes to main, one of the non-negotiables from Chapter 3.
And I had, with my own hands, written an instruction telling the agent the specific phrasing of git push that
slips past my own guard. Not maliciously; I'd written it while reasoning about git ergonomics and never once
connected it to the security surface. But the effect was a coaching note for evasion, sitting in the file whose
entire job is to shape the agent's behavior. The guard said "you cannot push to main." The instruction file
said "here is how to push to main without the guard noticing." The agent, dutifully following its instructions,
would have been taught to defeat my own boundary.
That finding is the door into this chapter, because it's the most vivid possible proof of its thesis: when you run more than one agent harness on one machine, the security of the whole is the security of the weakest harness, and the weak points are not always in the code. Sometimes they are in the prose you wrote to govern it.
One substrate, two doors
Claude Code and Codex don't live in separate worlds. They share this machine, they share me as operator, and critically they share a substrate: the same bridge-db, the same operator control plane, the same engraph knowledge vault. Both agents read those databases. Both agents write them. Which means the shared state is a room with two doors, and an attacker, or a prompt injection, or a confused agent following a poisoned instruction, doesn't care which door is stronger. They walk in through the weaker one.
And the doors weren't equally strong. Claude Code is my reference implementation: a deep stack of hooks, a deny
list, the whole apparatus of Chapter 3, hardened over many sessions. Codex was, in the language of the parity
audit, one hardening pass behind, and the gap was asymmetric in exactly the dangerous direction. The audit
laid the two harnesses side by side across eight protection guarantees: the safe-by-default lane, hook
self-protection, secret-literal blocking, credential-read blocking, push-to-main, destructive database ops,
dangerous shell commands, network egress. On guarantee after guarantee, Codex was the softer target. Its
default lane ran with broad access and proceeded on failure. Its hook self-protection was warn-only, a plain
cp could overwrite its guards. It had no command-line secret block. Its push-to-main guard caught --force
but not the bare push the instruction file helpfully recommended. The conclusion the audit reached is the
sentence I'd put on the cover of this chapter if I could: hardening the strong harness while the weak one
runs soft is not defense in depth. It is a deadbolt on the front door and an open window on the side, and
calling the house secure.
The deepest version of the finding was the sensitive-path lists. The set of credential directories that must
never be read was maintained in three separate hand-edited places: Claude Code's Bash hook, Claude Code's
permissions.deny, and Codex's own path regex. Three lists, three authors-in-time, one job. And they had
drifted, provider by provider, a path present in one, missing from another, the exact entropy decay you'd
predict from any fact duplicated across three files that must be updated in lockstep by a human who will,
eventually, forget one. Three lists that must agree and have no mechanism forcing them to is not three controls.
It's three opportunities to be wrong.
The fix that can't drift
The proposed remedy was the one structural move that actually defeats drift: stop duplicating the list. Put the canonical sensitive-path policy in one shared policy file and have every guard, in both harnesses, read it live. Not copy it. Read it, at decision time, from the single source. The egress guard already worked this way; both harnesses already consulted that one policy file for network decisions, so the template was proven. Extend it to the sensitive-path lists and the three-way drift becomes structurally impossible, because there's only one list and both harnesses are looking at it.
That's the fix the parity audit proposed. And here's where this chapter has to be more honest than most, because of when it was written and what I did about it.
The gap as found, and the gap as it stands
Every finding I've described was, as of the parity session, staged: proposed, drafted, not applied. The
canonical-policy fix was a document in a staging directory. The instruction-file evasion line was identified
but still sitting in AGENTS.override.md. The soft Codex defaults were diagnosed, not changed. A lesser book
would stop there, narrate the findings, and let you assume they were fixed, which is precisely the stale-
dossier failure of Chapter 4, where intelligence about a system silently goes out of date the moment the system
moves. The outline for this chapter even carried a warning to myself: before publication, re-verify, are the
holes still open?
So I re-verified against live disk on 2026-06-12, reading the current state of both harnesses instead of the
staged-fix list. The keystone fix had shipped: one shared sensitive_paths block in mcp-gate-policy.json,
read live by both Claude Code and Codex with fail-closed fallback. The push-to-main evasion line was gone;
Codex's default lane, hook self-protection, command-line secret block, push guard, database guard, sandbox
network default, and config-integrity guard had all hardened. Not everything closed, and the open cells matter:
dangerous-command parity remained partial, keychain-backed MCP credential storage was still upstream-blocked, one
redundant Claude Code guard still needed retirement, and two runtime-only checks stayed CANNOT-VERIFY. The
table is the chapter: live readback, not staged intent.
| Finding (as found) | Status on disk, 2026-06-12 |
|---|---|
| Sensitive-path list maintained in three places, drifting | CLOSED: one mcp-gate-policy.json, both harnesses read it live |
AGENTS.override.md coached push-to-main evasion | CLOSED: line replaced with PR-checkpoint guidance |
| Codex default lane: broad access, proceed-on-failure | CLOSED: workspace-write + on-request |
| Codex hook self-protection was warn-only | CLOSED: hard deny, incl. interpreter-write clause |
| Codex had no command-line secret block | CLOSED |
Codex push-to-main: blocked --force only | CLOSED: bare push + +refspec now blocked |
| Codex DB guard: ANSI clients only | CLOSED: Mongo / Redis / non-ANSI now covered |
Codex sandbox: blanket network_access = true | CLOSED: now false |
| Codex had no config-integrity guard | CLOSED: session snapshot + truncation check |
| Codex dangerous-command parity | PARTIAL: mkfs/dd/kill added; fork-bomb + rm -rf compound-split still open |
| MCP credential storage hardening | STILL-OPEN: keychain-backed storage path not yet merged |
CC redundant remote-command-guard.sh | STILL-OPEN: still wired, superseded by egress guard (hygiene) |
| Hook timeout; live OAuth-token expiry | CANNOT-VERIFY: needs runtime / credential read |
Postscript, 2026-07-11. I re-checked the movable rows against live disk and against the Codex modernization program that closed today, and most of the table held. The dangerous-command parity gaps are still open: mkfs, dd, and kill -9 -1 are blocked, but there is still no fork-bomb pattern and no general local recursive-delete block. Keychain-backed MCP credential storage is still unmerged; command-line auth uses the keyring while MCP OAuth credentials remain in a file. The redundant guard script was not retired, because the 2026-07-11 harness sweep found nothing safe to cut. The two runtime-only checks stay CANNOT-VERIFY. And the modernization program itself, scoped to MCP readiness and automation rather than the guard floor, did not touch any of these, which is the right kind of boring: a ten-wave overhaul that knew which floor not to renovate.