All Posts

Tamper-evident audit trails for agent sessions

Agent authorization needs an audit trail that can show whether the session record changed after the fact. AgentTrust ID records session decisions in a hash chain, then verifies the stored head against the recorded event list and session counters.

Tamper-evident decision chains

A tamper-evident chain links each decision to the decisions before it. A new session starts with a genesis hash of 64 zeroes. Each recorded authorization decision extends the chain:

H_new = SHA-256(H_old || event)

In the current session code, the event is deterministic JSON with the action effect, result, and total call count:

{"effect":"destructive","result":"denied","total":3}

The session stores the current audit_chain_hash and the ordered audit_chain_events list. Replaying the list from the genesis hash should produce the stored head.

Plain log row risk

A database table can store records, but a row by itself does not show whether earlier rows were removed or edited. If an attacker or bug deletes one decision row, later rows can still look unchanged.

A chain makes that gap visible. Change one event and the final hash changes. Remove one event and the replay no longer matches the stored head. Count a decision without recording the event and the event count no longer matches total_calls.

Accountability verification example

The accountability endpoint verifies one session for the authenticated org.

import json
import os
import urllib.request

base_url = os.environ.get("AGENTTRUST_URL", "http://localhost:8080")
api_key = os.environ["AGENTTRUST_API_KEY"]
session_id = "session_123"

body = json.dumps({"session_id": session_id}).encode("utf-8")

request = urllib.request.Request(
    f"{base_url}/api/v1/accountability/verify",
    data=body,
    method="POST",
    headers={
        "Content-Type": "application/json",
        "X-API-Key": api_key,
    },
)

with urllib.request.urlopen(request, timeout=10) as response:
    result = json.loads(response.read().decode("utf-8"))

assert result["integrity_checks"]["session_active"] is True
assert result["integrity_checks"]["chain_consistent"] is True
assert result["integrity_checks"]["scope_unchanged"] is True
assert result["valid"] is True

Pass expected_chain_hash when you have saved a known-good head and want to check drift.

Prevention

Replay the chain from recorded events and compare it to the stored head. Then check the event count against total_calls. Those two checks catch different failures.

Replay catches edited, inserted, or removed events. The count check catches a decision that incremented the session counter without leaving an event in the chain. The verifier recomputes the scope hash and compares it to the record built from current session scope.

Solving this with AgentTrust ID

AgentTrust ID records session metrics and extends the chain during RecordCall. Each action check advances counters and chain state together. The verifier returns a structured response with session_active, chain_consistent, scope_unchanged, and chain_drift.

The dashboard's Accountability page uses this verification model. It gives an operator a pass or fail answer for a session record instead of asking them to trust the log table.

To record decisions from your agent tools, start with the SDK guide. To talk through audit review and session verification, join the waitlist.