Audit Logging
Claw GRC maintains a comprehensive, tamper-evident audit log that records every state change in the system. The audit log uses cryptographic chain hashing to provide mathematical proof that no entries have been modified or deleted.
What Gets Logged
Every action by every actor (human or agent) is recorded:
| Action Category | Examples |
|---|---|
| Evidence | Upload, validate, reject, soft-delete |
| Policies | Create, update, approve, archive |
| Risks | Create, update score, change treatment |
| Findings | Create, update status, resolve |
| Tickets | Create, assign, update status, close |
| Agents | Register, update, deregister, key rotation |
| MCP | Session create, tool invocations, session close |
| Members | Invite, role change, deactivate |
| Trust | Score recalculation, threshold violations |
Audit Log Schema
Each audit log entry contains:
| Field | Type | Description |
|---|---|---|
id | UUID | Unique entry identifier |
org_id | UUID | Organization (tenant) scope |
actor_type | string | user or agent |
actor_id | UUID | Who performed the action |
action | string | What happened (e.g., evidence.created, agent.key_rotated) |
resource_type | string | Type of affected resource (evidence, agent, policy, etc.) |
resource_id | UUID | ID of the affected resource |
metadata | JSONB | Action-specific details (changes, previous values, etc.) |
chain_hash | string | SHA-256 chain hash linking to previous entry |
created_at | timestamp | When the action occurred |
Chain Hashing Algorithm
Each audit log entry includes a chain_hash that links it to the previous entry:
chain_hash_N = SHA-256(chain_hash_{N-1} + ":" + JSON(entry_N))
The first entry in the chain uses "genesis" as the previous hash.
Verification
To verify the audit log integrity:
- Fetch all entries in chronological order
- Starting from the first entry, compute the expected hash using
"genesis"as the previous hash - Compare the computed hash with the stored
chain_hash - Use the stored
chain_hashas the previous hash for the next entry - If any computed hash does not match the stored hash, the chain is broken at that point
What Tampering Looks Like
If someone modifies entry #500 in a chain of 1,000 entries:
- The hash of entry #500 would change
- Entry #501's hash depends on entry #500's hash, so it would also be invalid
- This cascade continues through entries #502, #503, ..., #1000
- Running verification would immediately detect the break at entry #500
Immutability Enforcement
The audit_log table is protected by database triggers that prevent modification:
-- Block UPDATE operations
CREATE OR REPLACE FUNCTION prevent_audit_log_update()
RETURNS TRIGGER AS $$
BEGIN
RAISE EXCEPTION 'audit_log rows are immutable and cannot be updated';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_audit_log_update
BEFORE UPDATE ON audit_log
FOR EACH ROW EXECUTE FUNCTION prevent_audit_log_update();
-- Block DELETE operations
CREATE OR REPLACE FUNCTION prevent_audit_log_delete()
RETURNS TRIGGER AS $$
BEGIN
RAISE EXCEPTION 'audit_log rows are immutable and cannot be deleted';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_audit_log_delete
BEFORE DELETE ON audit_log
FOR EACH ROW EXECUTE FUNCTION prevent_audit_log_delete();
-- Block TRUNCATE
REVOKE TRUNCATE ON audit_log FROM PUBLIC;
Combined with FORCE ROW LEVEL SECURITY, this means:
- No one can modify an existing entry (trigger blocks UPDATE)
- No one can delete an entry (trigger blocks DELETE)
- No one can truncate the table (TRUNCATE privilege revoked)
- Even the table owner is subject to these restrictions
Exporting Audit Logs
CSV Export
Generate a CSV export for your auditors:
curl -X POST https://api.clawgrc.com/api/v1/reports \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "audit_trail",
"start_date": "2025-01-01T00:00:00Z",
"end_date": "2025-01-31T23:59:59Z",
"format": "csv"
}'
The CSV includes all columns including chain_hash, allowing independent verification.
Direct Query (Admin)
For database administrators:
-- Set tenant context (required due to FORCE RLS)
SELECT set_config('app.current_org', 'YOUR_ORG_UUID', true);
-- Query audit log
SELECT id, actor_type, actor_id, action, resource_type,
resource_id, metadata, chain_hash, created_at
FROM audit_log
WHERE created_at >= '2025-01-01'
AND created_at < '2025-02-01'
ORDER BY created_at ASC;
Dual Audit System
Claw GRC maintains two chain-hashed logs:
| Log | Table | Purpose | What It Tracks |
|---|---|---|---|
| Audit Log | audit_log | Compliance auditing | All state changes (CRUD operations) |
| Agent Interactions | agent_interactions | Agent governance | All MCP tool invocations and agent-to-agent calls |
Both use the same chain hashing algorithm and the same immutability protections.
Compliance Framework Coverage
The audit logging system satisfies requirements across multiple frameworks:
| Framework | Control | Requirement |
|---|---|---|
| SOC 2 | CC7.2 | Monitor system components for anomalies |
| SOC 2 | CC7.3 | Evaluate security events |
| ISO 27001 | A.12.4.1 | Event logging |
| ISO 27001 | A.12.4.3 | Administrator and operator logs |
| NIST 800-53 | AU-2 | Auditable events |
| NIST 800-53 | AU-3 | Content of audit records |
| NIST 800-53 | AU-9 | Protection of audit information |
| NIST 800-53 | AU-10 | Non-repudiation |
| HIPAA | 164.312(b) | Audit controls |
| PCI DSS | 10.1 | Audit trails for system components |
| PCI DSS | 10.5 | Secure audit trails |
Best Practices
- Export monthly -- Download audit trail CSVs monthly and store them in an independent, immutable storage system (e.g., AWS S3 with Object Lock).
- Verify quarterly -- Run chain hash verification quarterly to confirm no tampering has occurred.
- Retain for compliance -- SOC 2 requires 1 year minimum; PCI DSS requires 1 year online + 1 year archived; HIPAA requires 6 years.
- Monitor gaps -- Alert on time gaps in the audit trail that might indicate issues.
- Correlate across logs -- Use both the audit log and agent interactions table to build a complete picture of activity.