{"slug": "i-tried-to-forge-my-own-ai-agent-s-audit-log", "title": "I tried to forge my own AI agent's audit log", "summary": "A developer built an open-source audit and governance layer for AI agents, comprising a versioned \"passport\" identity card, a scoped machine-readable mandate, a pre-model-call permission gate, and an append-only hash-chained JSONL journal. Testing the journal by tampering with a record — flipping a \"deny\" decision to \"allow\" — the verification tool detected the edit and named the specific record, demonstrating tamper-evident logging. The work responds to Cloud Security Alliance and 2026 State of AI Agent Security findings that nearly half of production agents lack monitoring and roughly a fifth have their own identity.", "body_md": "Half the organisations running AI agents have already had one step outside its\n\npermissions. Nearly half of the agents in production have no monitoring at all.\n\nOnly about a fifth treat an agent as something with an identity of its own.\n\nThose are numbers from the Cloud Security Alliance and the 2026 State of AI\n\nAgent Security report, and they describe the same gap from three directions:\n\nagents act, and nobody can say afterwards what exactly they did.\n\nI spent a few weeks building the missing piece, and then spent an afternoon\n\nattacking it. This is what survived.\n\n**A passport.** Not a config file — a card for one *version* of an agent that\n\nsays who built it, who runs it, what it does, and what it will never do:\n\n```\n{\n  \"agent_id\": \"kepil.leads.v1\",\n  \"purpose\": \"Handle inbound requests and hand qualified ones to a person\",\n  \"does_not\": [\n    \"never promises prices or deadlines on the company's behalf\",\n    \"never sends invoices or contracts\",\n    \"never exports the customer base outside the perimeter\"\n  ],\n  \"risk_class\": \"medium\",\n  \"autonomy_class\": \"medium\",\n  \"risk_review\": { \"last\": \"2026-09-14\", \"next_due\": \"2027-09-14\" }\n}\n```\n\n(Trimmed for readability — a real card also carries the operator, the model list,\n\nthe rationale for each class, an incident log and the passport's own hash. One\n\ncaveat before you install: the runtime, the panel and these field *values* are\n\ncurrently in Russian. The format and the code are not language-specific, but you\n\nwill be reading «средний» where this article says \"medium\".)\n\nA new version is a new card. The old one is kept forever, because the question\n\n\"what was this agent allowed to do in March\" has to have an answer.\n\n**A mandate.** A machine-readable power of attorney for one job — not a\n\npermanent grant:\n\n```\n{\n  \"mandate_id\": \"mnd-0001\",\n  \"allowed_actions\": [\"read:inbox\", \"read:crm\", \"generate:reply\", \"send:message\",\n                      \"write:crm\", \"generate:summary\", \"send:handoff\"],\n  \"allowed_systems\": [\"crm.local\", \"whatsapp.local\"],\n  \"forbidden_actions\": [\"sign:*\", \"pay:*\", \"export:database\", \"send:bulk\"],\n  \"human_confirmation_required\": [\"send:*\", \"write:crm\", \"publish:*\"],\n  \"limits\": { \"messages\": 300, \"llm_cost_kzt\": 4000 },\n  \"valid_until\": \"2026-09-21T10:09:45\"\n}\n```\n\nAnything not explicitly allowed is refused. That sounds obvious until you look\n\nat how agents are actually wired: one shared API key, full network access, and\n\na prompt asking nicely.\n\n**A gate.** One place an agent touches the outside world, and every action is\n\nchecked *before* a model is even called. The checks run in a fixed order:\n\npassport active, mandate valid, action allowed, system allowed, limits not\n\nspent, irreversible or not. Any error inside the check means refusal — never a\n\npass. A false refusal costs a retry; a false pass costs a breach notification.\n\n**A journal.** Append-only JSONL where each record carries the hash of the one\n\nbefore it:\n\n```\n{\"seq\":10241,\"ts\":\"2026-09-12T11:42:07+05:00\",\"agent_id\":\"kepil.leads.v1\",\n \"action\":{\"type\":\"send:message\",\"target\":\"whatsapp.local\"},\n \"decision\":\"await_human\",\"cost_kzt\":0,\n \"prev_hash\":\"sha256:c61d8b…\",\"hash\":\"sha256:d5ade5…\"}\n```\n\nValues of personal data never enter it. Types, counts and hashes do.\n\nA log you cannot verify is a diary. So I wrote a record, then went back and\n\nedited it the way someone covering their tracks would — flipping a refusal into\n\nan approval:\n\n``` bash\n# the agent tried to sign a document; the gate refused\n$ npx proofbyte-agent-trace verify journal.jsonl\nIntegrity confirmed. Records: 4\nChain head: sha256:d58c204ff2a37b093307b95b3d9fcc4edc510cd6062622db10b3461a4341dd3f\n\n$ sed -i 's/\"decision\": \"deny\"/\"decision\": \"allow\"/' journal.jsonl\n$ npx proofbyte-agent-trace verify journal.jsonl\nIntegrity BROKEN. Record 3: record content changed after it was written\n```\n\nTwo things matter here.\n\nThe first is that it names the record, not just the file. Hash chaining gives\n\nyou that for free, and it is the difference between \"something is wrong\" and\n\n\"here is what was changed\".\n\nThe second is subtler. **The verifier is a different program in a different language, written by nobody in particular.** The writer is Python; the verifier\n\n`0.0` as `\"0.0\"` and JavaScript as `\"0\"`, so the same record hashed\nKepil ships an MCP server, a JSON API and an n8n node. All three can create\n\norders, run steps, ask whether an action is permitted, and read the journal.\n\nNone of them can confirm.\n\nIf a model could call `confirm`, the human would drop out of the chain: the\n\nagent would be approving its own irreversible actions, and every guarantee\n\nabove would become decoration. So the confirmation card goes to a person — the\n\npanel, or Telegram with two buttons — and there is a test whose only job is to\n\nfail if `confirm` ever appears in the tool list.\n\nI find this the most useful thing to say about the whole project. Everyone\n\nadvertises what their agent framework can do. The interesting part is what it\n\nrefuses to do even when you ask it nicely.\n\nRecording is table stakes. Agent-governance products — Workday's Agent\n\nPassport, Okta, Credo AI — all write logs. None of them put anything back.\n\nBecause the journal is a graph of actions and every profession declares its\n\ncompensating action, you can walk it backwards:\n\n```\n\"rollback\": {\n  \"send:message\": \"send a correcting message\",\n  \"write:crm\": \"restore the previous state of the deal from the journal\",\n  \"publish:complaint\": \"\"\n}\n```\n\nThe empty string is the important entry. It means *this cannot be undone*.\n\nPick a window in the panel — the last hour, say — and the pass runs from the\n\nmost recent action backwards, applying each compensating action in turn, and\n\nstops at the first step whose compensation is that empty string. It then reports\n\nboth halves: what it undid, and what it could not. The panel shows the same\n\nthing **before** you press the button, naming the step where the pass will stop:\n\n```\nWill undo 2 actions. The pass will stop at \"Search for tailored requirements\" —\nthat action is irreversible, and nothing before it will be undone.\n```\n\nAn undo promise that quietly fails is worse than no undo at all, so there is a\n\ntest whose only job is to fail if the preview and the engine ever disagree.\n\nOne more thing follows from this. A rollback is written to the journal as an\n\n*operator's* decision, so neither the MCP server nor the JSON API can perform\n\none — same reasoning as `confirm`. An agent undoing its own actions would be\n\nsigning in somebody else's name.\n\nThe core has zero dependencies. Not \"few\" — zero: it is the Python 3.11+\n\nstandard library, and CI fails the build if a third-party import appears. Two\n\nreasons. It installs inside an air-gapped perimeter where `pip install` from\n\nthe internet is not an option. And a tool that sees every action an agent takes\n\nshould not drag a hundred transitive packages behind it.\n\nState is JSON files. No database. You can open them, read them, and attach them\n\nto a dispute — which is the whole point of the exercise.\n\nAlpha, 115 tests, AGPL-3.0. It does not run agents for you: it is the layer\n\nthat says what they may do and records what they did.\n\n```\npip install kepil\npython -m kepil.admin\n```\n\nIf you are running agents in production and can answer \"what did agent X do on\n\n9 September, and who allowed it\" — I would genuinely like to know how. If you\n\ncannot, that is the gap this was built for.", "url": "https://wpnews.pro/news/i-tried-to-forge-my-own-ai-agent-s-audit-log", "canonical_source": "https://dev.to/olegvdv/i-tried-to-forge-my-own-ai-agents-audit-log-3chh", "published_at": "2026-09-14 05:21:22+00:00", "updated_at": "2026-09-14 05:56:58.100312+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "ai-policy", "developer-tools", "ai-infrastructure"], "entities": ["Cloud Security Alliance", "proofbyte-agent-trace"], "alternates": {"html": "https://wpnews.pro/news/i-tried-to-forge-my-own-ai-agent-s-audit-log", "markdown": "https://wpnews.pro/news/i-tried-to-forge-my-own-ai-agent-s-audit-log.md", "text": "https://wpnews.pro/news/i-tried-to-forge-my-own-ai-agent-s-audit-log.txt", "jsonld": "https://wpnews.pro/news/i-tried-to-forge-my-own-ai-agent-s-audit-log.jsonld"}}