Skip to main content

Tamper-evident Merkle audit chain for AI agent tool calls

Project description

merkle-audit

Tamper-evident audit logging for AI agent tool calls.

Every action your agent takes — web searches, file writes, API calls, payments — is committed to an append-only hash chain. If any historical entry is modified, all subsequent entries become cryptographically invalid. You get a verifiable record of exactly what your agent did and in what order, with no external infrastructure required.

from merkle_audit import log_event

entry = log_event(
    "tool_executed",
    {"tool": "web_search", "query": "current ETH price"},
    actor="research-agent-1",
)
print(entry.leaf_hash)   # sha256 of (prev_root + action + payload + timestamp)
print(entry.mmr_root)    # running root — commits to the entire history

Why this matters for AI agents

When an agent takes actions in the world — spending money, sending messages, modifying files — you want a record that:

  1. Can't be quietly edited. If an agent (or anyone with DB access) modifies a past entry, the hash chain breaks from that point forward.
  2. Is ordered. The chain encodes sequence: entry N depends on entry N-1's root.
  3. Is lightweight. No external services, no consensus protocol. Just SHA-256 and a list.
  4. Persists across restarts. Attach SQLite persistence with one line.

Installation

pip install merkle-audit

No required dependencies — pure Python stdlib.

Optional: SQLite persistence is included. FastAPI integration is available as an extra:

pip install merkle-audit[fastapi]

Usage

Basic

from merkle_audit import AuditChain

chain = AuditChain()

e1 = chain.append("tool_executed", {"tool": "read_file", "path": "/etc/hosts"})
e2 = chain.append("tool_executed", {"tool": "write_file", "path": "/tmp/out.txt"})
e3 = chain.append("approval_requested", {"reason": "about to send email"})

print(f"{chain.leaf_count()} entries, root: {chain.current_root()[:16]}...")

Persist to SQLite

from merkle_audit import get_chain
from merkle_audit.persister import install_sqlite_persister

# Call once at startup — all subsequent log_event() calls are persisted
install_sqlite_persister("/var/lib/myapp/audit.db")

from merkle_audit import log_event
log_event("payment_sent", {"to": "0xabc...", "amount_usd": 50.00}, actor="billing-agent")

Verify an entry

from merkle_audit import AuditChain

chain = AuditChain()
e0 = chain.append("first_action", {"data": "hello"})
e1 = chain.append("second_action", {"data": "world"})

genesis = "0" * 64
assert chain.verify_entry(e0, prev_root=genesis)
assert chain.verify_entry(e1, prev_root=e0.mmr_root)

# Simulate tampering
from dataclasses import replace
tampered = replace(e0, action_type="something_else")
assert not chain.verify_entry(tampered, prev_root=genesis)  # False — detected

FastAPI endpoint

from fastapi import FastAPI
from merkle_audit.fastapi import router

app = FastAPI()
app.include_router(router, prefix="/audit")
# GET /audit/chain → {"leaf_count": 42, "current_root": "a3f9..."}

How it works

Each entry commits to everything that came before it:

leaf_hash = SHA256(prev_root + ":" + action_type + ":" + payload_hash + ":" + timestamp)
new_root  = SHA256(prev_root + ":" + leaf_hash)

The mmr_root after N entries is a single 32-byte value that cryptographically summarises the entire history. To verify entry K, you only need its stored leaf_hash and the mmr_root from the entry immediately before it.

This is a simplified Merkle Mountain Range: append-only, no peak merging, optimised for sequential audit logging rather than inclusion proofs in large trees.

ChainEntry fields

Field Type Description
leaf_index int Sequential position (0-based)
leaf_hash str SHA-256 hex — tamper-evident commitment
mmr_root str SHA-256 hex — running root over all entries
payload_hash str SHA-256 hex of the serialised payload
action_type str Caller-defined event label
actor str Who performed the action
created_at float Unix timestamp

Running tests

pip install merkle-audit[dev]
pytest

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

merkle_audit-0.1.0.tar.gz (9.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

merkle_audit-0.1.0-py3-none-any.whl (9.0 kB view details)

Uploaded Python 3

File details

Details for the file merkle_audit-0.1.0.tar.gz.

File metadata

  • Download URL: merkle_audit-0.1.0.tar.gz
  • Upload date:
  • Size: 9.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.16.5 cpython/3.12.3 HTTPX/0.28.1

File hashes

Hashes for merkle_audit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e5e33df2323799e860bccc674a799ad6ab8ad9acfcaf3ad4a6483082bcbf67b8
MD5 c103f8bfa3ee63e767a524179f68fb0c
BLAKE2b-256 1aa59c083446984cbbabb63ae9ba6ce0a1cfd878378f0c6cc9f4775c04eaaaca

See more details on using hashes here.

File details

Details for the file merkle_audit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: merkle_audit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 9.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: Hatch/1.16.5 cpython/3.12.3 HTTPX/0.28.1

File hashes

Hashes for merkle_audit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a637b5693ac6be8535ccb22843db64178566879cd314472739e696b09ef0dfce
MD5 b4bcfc4d8c471181ce278477fd8f8c96
BLAKE2b-256 6c135e47abb636adab6ab5e9f122d96c30fa28fab04e85798ec748bc56c3708e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page