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 Type | Trigger | Logged Data |
|---|---|---|
mcp.handshake | Agent opens a session | Agent ID, protocol version, capabilities, supported tools |
mcp.tool.* | Agent invokes a tool | Method, parameters, result or error, duration |
mcp.session_closed | Agent or system closes session | Session ID, closure reason |
agent_invoke | Agent-to-agent invocation | Source 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, orqueued - 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:
- Its hash would change because the entry data changed
- The next entry's hash would also change because it depends on the previous hash
- This cascade continues through every subsequent entry
- 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:
- Navigate to Reports > Audit Trail Export
- Select the date range
- Choose the format (CSV)
- 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:
UPDATEandDELETEoperations are blocked by database triggersTRUNCATEis blocked- Only
INSERTis 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.