Skip to main content

Agent Audit Trail

Every interaction between agents and the Claw GRC platform is recorded in a tamper-evident audit trail using cryptographic chain hashing. This provides auditors with mathematical proof that the interaction history has not been modified.

What Gets Logged​

Every MCP operation generates an audit trail entry:

Interaction TypeTriggerLogged Data
mcp.handshakeAgent opens a sessionAgent ID, protocol version, capabilities, supported tools
mcp.tool.*Agent invokes a toolMethod, parameters, result or error, duration
mcp.session_closedAgent or system closes sessionSession ID, closure reason
agent_invokeAgent-to-agent invocationSource agent, target agent, method, trust check result

Each entry includes:

  • Organization ID -- Tenant scope
  • Agent ID -- Which agent performed the action
  • Interaction type -- What happened
  • Request payload -- What was sent
  • Response payload -- What was returned
  • Status -- success, error, rejected, or queued
  • Duration (ms) -- How long the operation took
  • Chain hash -- Cryptographic link to the previous entry

Chain Hashing​

Algorithm​

Each audit trail entry includes a chain_hash computed from the previous entry's hash and the current entry's data:

import hashlib
import json

def compute_chain_hash(previous_hash: str, entry_data: dict) -> str:
"""Compute SHA-256 chain hash from previous hash and current entry."""
entry_json = json.dumps(entry_data, sort_keys=True, default=str)
combined = f"{previous_hash}:{entry_json}"
return hashlib.sha256(combined.encode("utf-8")).hexdigest()

The first entry in the chain uses "genesis" as the previous hash.

How It Works​

Each hash is computed as:

Hash_N = SHA-256(Hash_{N-1} + ":" + JSON(Entry_N))

Tamper Detection​

If any entry in the chain is modified:

  1. Its hash would change because the entry data changed
  2. The next entry's hash would also change because it depends on the previous hash
  3. This cascade continues through every subsequent entry
  4. Comparing the stored hashes with recomputed hashes reveals the tampering point

Concurrency Protection​

To prevent race conditions from corrupting the chain, the platform uses PostgreSQL advisory locks:

SELECT pg_advisory_xact_lock(hashtext('interactions:' || org_id));

This ensures that within each organization, chain hash computation is serialized.

Verifying Chain Integrity​

Manual Verification​

To verify the audit trail integrity for an organization:

-- Set the tenant context
SELECT set_config('app.current_org', 'YOUR_ORG_ID', true);

-- Fetch all interactions in order
SELECT id, chain_hash, source_agent_id, interaction_type,
request_payload, response_payload, status, duration_ms,
created_at
FROM agent_interactions
WHERE org_id = 'YOUR_ORG_ID'
ORDER BY created_at ASC, id ASC;

Then replay the chain hash computation on each entry and compare with the stored hash. Any mismatch indicates tampering.

Programmatic Verification​

import hashlib
import json

def verify_chain(interactions: list[dict]) -> tuple[bool, int | None]:
"""Verify the integrity of an interaction chain.

Returns (True, None) if valid, or (False, index) if
the chain breaks at the given index.
"""
previous_hash = "genesis"

for i, entry in enumerate(interactions):
entry_data = {
"org_id": entry["org_id"],
"agent_id": entry["source_agent_id"],
"interaction_type": entry["interaction_type"],
"request_payload": entry["request_payload"],
"response_payload": entry["response_payload"],
"status": entry["status"],
"duration_ms": entry["duration_ms"],
}
entry_json = json.dumps(entry_data, sort_keys=True, default=str)
expected_hash = hashlib.sha256(
f"{previous_hash}:{entry_json}".encode("utf-8")
).hexdigest()

if expected_hash != entry["chain_hash"]:
return False, i

previous_hash = entry["chain_hash"]

return True, None

Audit Trail Export​

Export the audit trail as CSV for external review:

  1. Navigate to Reports > Audit Trail Export
  2. Select the date range
  3. Choose the format (CSV)
  4. Download the export

The export includes all columns including chain hashes, allowing auditors to independently verify the chain integrity.

Dual Audit System​

Claw GRC maintains two audit systems:

1. Agent Interactions (agent_interactions table)​

  • Records all MCP and agent-to-agent interactions
  • Chain-hashed for tamper detection
  • Includes full request/response payloads
  • Used for trust score computation

2. Audit Log (audit_log table)​

  • Records all state changes (creates, updates, deletes)
  • Chain-hashed for tamper detection
  • Covers both human and agent actions
  • Used for compliance reporting

Both tables have FORCE ROW LEVEL SECURITY ensuring tenant isolation even for database administrators.

Immutability​

The audit_log table is protected against modification:

  • UPDATE and DELETE operations are blocked by database triggers
  • TRUNCATE is blocked
  • Only INSERT is allowed
  • The table owner role is subject to these restrictions (FORCE ROW LEVEL SECURITY)

This ensures that once an audit entry is written, it cannot be modified or deleted by any user, including database administrators.

Best Practices​

  • Export regularly -- Download audit trail CSVs monthly and store them in an independent system (e.g., a separate cloud storage bucket with immutable retention).
  • Verify periodically -- Run chain verification monthly to confirm no tampering has occurred.
  • Retain for compliance -- Most frameworks require audit log retention for 1-7 years. Configure your retention policy accordingly.
  • Monitor for gaps -- Alert on time gaps in the audit trail that might indicate deleted entries.