OWASP AST09: execution receipts for AI agents

AST09 is the "missing execution receipts" entry in the OWASP Agentic Skills Top 10 — the risk that an AI agent takes an action and leaves behind nothing a third party can trust. This page explains what the entry asks for, the bilateral receipt pattern that is its normative guidance, and how to satisfy it in a few lines of code.

What AST09 is

The OWASP Agentic Skills Top 10 catalogs the ways autonomous agent "skills" go wrong. AST09 is the accountability gap: when an agent invokes a tool, moves money, or reads a record, the operator usually keeps an ordinary log — on their own infrastructure, editable and deletable by them. Anyone auditing that log has to trust the exact party whose behavior they are auditing. AST09 names that as a distinct risk class and asks for records that survive scrutiny by an outside party.

The bilateral receipt pattern (the normative guidance)

The pattern that answers AST09 is a pair of signed receipts per action. It is normative guidance in the AST09 entry (PR #35, merged by project lead Ken Huang):

Both records are canonicalized with JCS (RFC 8785) so any conformant implementation serializes them to identical bytes, then signed with Ed25519 and hash-chained. The concrete hash construction that links the two receipts — the action_ref — is published in the OWASP solutions catalog (PR #38) rather than the normative page itself. Because action_ref is derived from the content of the action (agent id, action type, scope, timestamp), anyone can recompute it, and the operator cannot forge or quietly re-point it.

Why this clears the bar that a log does not

Implementing AST09 receipts

Nobulex is the open-source (MIT) reference implementation of the bilateral receipt pattern. Adding conformant execution receipts to an existing agent function is one decorator:

from nobulex import track

@track(agent_id="billing-bot")
def charge(invoice_id: str, amount_cents: int):
    ...  # your existing code, unchanged

Each call now emits an admission receipt and an outcome receipt, linked by action_ref, signed and chained. A denied action still produces a signed admission record, so "the agent was stopped" is auditable too — not a silent hole.

Verifying a receipt

Verification recomputes action_ref from the record and checks the Ed25519 signature against the agent's published public key. It runs fully offline — no callback to the operator, the platform, or Nobulex:

from nobulex.agent import Agent

agent = Agent("billing-bot")
receipt = agent.act("charge", scope="invoice:042")
assert receipt.verify()  # recomputes action_ref, checks the signature, offline

Change one byte of a receipt and verify() returns false. That is the whole guarantee AST09 is after.

How AST09 relates to regulation

The same records that satisfy AST09 map cleanly onto the EU AI Act (Regulation (EU) 2024/1689), Article 12, which requires high-risk systems to keep automatically generated logs that enable post-hoc verification of how the system operated (enforcement from December 2, 2027). A log the operator can silently rewrite does not clear that bar; a signed, independently verifiable receipt does. See the EU AI Act compliance guide for the article-by-article mapping.

Getting started

pip install nobulex
# or
npm install @nobulex/core

There is a live tamper demo and a ten-line verify in the repository. For a step-by-step build, see the verifiable audit trail guide; for common questions, the FAQ.

Get started Star