Skip to content

Build1 publisher2 min readPublished

Flipping one deny to an allow broke the agent journal at record 3

The journal chains each record's hash into the next, so a one-line edit to a refusal surfaced as a numbered record; anyone able to rerun the writer can still recompute the chain and pass the check.

The Engineer · Build desk

Illustration accompanying Flipping one deny to an allow broke the agent journal at record 3

What happened

  • Half of organisations running AI agents have had one step outside its permissions and nearly half of production agents have no monitoring, per the Cloud Security Alliance and the 2026 State of AI Agent Security report.
  • The author spent a few weeks building a passport, a mandate, a gate and an append-only journal for one agent, then spent an afternoon attacking his own log.
  • A sed one-liner flipping a refusal into an approval turned "Integrity confirmed. Records: 4" into "Integrity BROKEN. Record 3: record content changed after it was written".
  • The writer is Python and the verifier is a separate TypeScript package on npm, whose test suite includes a journal produced by the Python writer.
  • That cross-language fixture caught Python serialising 0.0 as "0.0" while JavaScript writes "0", hashing the same record two ways, and number canonicalisation went into the format.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • cost The runtime, the panel and the field values ship in Russian today, so an English-reading team pays a translation pass before it can read its own gate decisions.
  • constraint Three of the seven actions the sample mandate permits match its human-confirmation patterns, so the agent's outbound path moves at the speed of whoever approves each send.
  • capability An investigator handed a record number can diff that record against a backup and say what the wording used to be; a file-level checksum mismatch only says the file moved.
  • precedent Shipping the verifier as a separate package a counterparty can run without the writer's code sets the bar the next agent audit format will be asked to clear.

The gate runs six checks in a fixed order before a model is called at all: passport active, mandate valid, action allowed, system allowed, limits not spent, irreversible or not [15][29]. An error inside any check is a refusal, never a pass [16]. The author, writing on dev.to as olegvdv, gave the reason in one line: "A false refusal costs a retry; a false pass costs a breach notification." [17][34]

A passport is one card per agent version, and old cards are kept forever so that the question of what the agent was allowed to do in March has an answer [6][8]. The mandate is the object the gate actually reads. mnd-0001 permits seven actions across crm.local and whatsapp.local, and forbids sign:*, pay:*, export:database and send:bulk [10][11]. Anything not explicitly allowed is refused [14]. It expires at 2026-09-21T10:09:45, and two ceilings cap the job: 300 messages and 4000 tenge of model spend [13]. That works out to about 13 tenge a message, so an agent averaging more than that hits the cost cap before the message cap [31].

He attacked his own file because, as he wrote, "A log you cannot verify is a diary." [20] Each record's hash covers the record before it [18], so an edit breaks every hash downstream of the one it touched. The same property sets the limit. Both the writer and the verifier are published code [24], so an operator who edits record 3 and then reruns the writer from record 3 onward produces a file that verifies clean [32]. The verify command does print a chain head, sha256:d58c204f... [21]. The article does not describe anchoring that value anywhere outside the file [33]. Until a counterparty holds the head, the journal constrains the agent and not the operator who runs it.

"Proof that only its own author can check is not proof," he wrote [35]. A log format checked only by the program that writes it fails in one direction: the writer and its verifier agree with each other and are both wrong about the encoding, and the first symptom is an honest record failing verification [26]. The journal also keeps values of personal data out of the file entirely; types, counts and hashes go in [19].

The population numbers at the top of the piece transfer only to a fleet wired the way the author describes common practice: "one shared API key, full network access, and a prompt asking nicely" [28]. A shop that already issues per-agent credentials and routes calls through a proxy is not the population those percentages measured [4].

What to watch

  • Whether the gate enforces the passport's risk_review next_due date of 2027-09-14 or only its active flag.
  • Whether a third implementation of the writer or verifier appears, and which canonicalisation bug it finds after numbers.
  • Whether the format gains a way for a counterparty, not the operator, to hold the chain head.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories