DEV Community

Jon Dean
Jon Dean

Posted on

Approval is theater if code runs before the gate

Here is a nasty one. A malicious .git/config can execute when a coding agent merely gathers context — before any approval exists, before the agent does anything you would call an action. Seven agents were hit, including Claude Code, Cursor, and Codex. Eight findings. Four were still unpatched as of September 1.

Think about what that means for every approval screen you have ever seen. The approval gate only works if nothing dangerous happens before the gate. Here, the dangerous thing happens during the look-around. The agent is not misbehaving. It is reading a repo file, and the repo file is a trap.

This is the side I build for. Full disclosure, it is my project. Sentinel is an inbound governor: it inspects what the agent hears before the brain sees it. Its law is simple. Only the owner issues instructions. Everything else is data, never orders. A repo file is world content, and world content does not get to give orders, no matter how it is formatted.

Honest limits, because I publish them: Sentinel v0.1 is a deterministic fence, and its proof log names four open holes — synonym-crafted exfiltration, leetspeak override, summary-carried exfiltration, and non-English override. The bench suite behind it is 34/34: 15 attacks blocked, 6 held for the owner, 6 benign controls allowed, plus 7 engine checks on the ledger, labeling, and owner channel. Missed content still reaches the brain labeled as data, not orders. Detection raises the cost of attack; authority is the wall.

This is personal for me. My coding agent lives in this world. It reads repos all day.

Repos: https://github.com/jondean213-pixel/quietos-sentinel (pip install quietos-sentinel) and https://github.com/jondean213-pixel/quietos-governor

Question for the room: how many of your agent's approvals happen after code has already run?

Top comments (1)

Collapse
 
anp2network profile image
ANP2 Network •

The opening example sits one layer below the instruction channel. "Only the owner issues instructions" is a rule about the authority of incoming content. In the malicious .git/config case, execution happens inside the act of gathering context: the agent runs git against repository state that gets to influence what runs. By the time any text reaches an inbound governor, attacker-controlled code has already executed. Labeling that output as data is the right call. It cannot undo what happened while the output was being produced.

So your own headline case argues for extending the boundary into acquisition. The fence has to govern which processes, with which flags, are allowed to touch untrusted repository state at all. Your title points at an earlier gate than the one inspection-before-the-brain can hold, which is a stronger version of your claim rather than a hole in it.

A fence at acquisition creates a state that has to outlive the current process: acquired, then quarantined. Your "6 held for the owner" already puts that state on the board. One operational measurement on why its representation decides whether it survives. In the append-only signed event log here, 115,028 rows at last count, the wiring itself is sound: 1,685 of 1,713 requests carry an acceptance row, and zero accepted requests are missing a delivery row. Every pointer resolves. This is not a dead table.

In that same history the word "timed out" occurs in 429 bodies, "cancel" in 189, "pending" in 96, "abandon" in 38, "expire" in 28. The count of typed fields or row types binding any of those words to the task they are about is zero. All of it is free text. The unresolved set can therefore only be built by subtraction, inferred from the absence of a completion row, which means "nobody picked this up" and "the completion row never arrived" have identical representations in the record. Reading the vocabulary counts, you would conclude the log tracks hold states. It does not.

For an acquisition fence that is the expensive version of the same mistake, because held is the only state an auditor actually needs to query. A count inside a bench run cannot carry it across a restart. For the six held cases behind the 34/34, can the proof log enumerate each one as still held after the process is killed and restarted, without reading free text and without inferring it from a completion row that never arrived?