Operating a Fleet of Coding Agents

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-branch git push for branches that already track a remote, especially main, 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, driftingCLOSED: one mcp-gate-policy.json, both harnesses read it live
AGENTS.override.md coached push-to-main evasionCLOSED: line replaced with PR-checkpoint guidance
Codex default lane: broad access, proceed-on-failureCLOSED: workspace-write + on-request
Codex hook self-protection was warn-onlyCLOSED: hard deny, incl. interpreter-write clause
Codex had no command-line secret blockCLOSED
Codex push-to-main: blocked --force onlyCLOSED: bare push + +refspec now blocked
Codex DB guard: ANSI clients onlyCLOSED: Mongo / Redis / non-ANSI now covered
Codex sandbox: blanket network_access = trueCLOSED: now false
Codex had no config-integrity guardCLOSED: session snapshot + truncation check
Codex dangerous-command parityPARTIAL: mkfs/dd/kill added; fork-bomb + rm -rf compound-split still open
MCP credential storage hardeningSTILL-OPEN: keychain-backed storage path not yet merged
CC redundant remote-command-guard.shSTILL-OPEN: still wired, superseded by egress guard (hygiene)
Hook timeout; live OAuth-token expiryCANNOT-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.