AI Platform Audit Logging: Immutable Compliance Trails for Enterprise
TL;DR — AI platform audit logging creates immutable, tamper-evident records of every AI action for compliance and governance. AxonFlow: "Audit logging is the layer that ties policy decisions, governed request flow, workflow activity, and connector behavior into one governance-oriented record." Labs.ai Audit Ledger: "write-once, append-only trail of every lifecycle task execution — signed with HMAC-SHA256 for tamper detection." KaireonAI: "Immutable audit trail with SHA-256 integrity chain. Logs are immutable — no DELETE, PUT, or PATCH operations permitted." LangWatch: multi-class retention (30 days / 1 year / 7 years) for SOC 2, GDPR, HIPAA. Kiteworks: "SIEM integration transforms audit data from a compliance artifact into a real-time governance tool." ibl.ai: "Enterprise AI without a complete audit trail is a liability." Retention: SOC 2 = 1 year, HIPAA = 6-7 years, EU AI Act = 7 years, SEBI = 5 years. Integrate with governance, role hierarchy, multi-tenant, and tenant suspension.
ibl.ai states the imperative: "Enterprise AI without a complete audit trail is a liability. When your AI agents make decisions, call APIs, execute code, or retrieve data, your compliance team needs to know exactly what happened, when, and why."
Maxim identifies the core problem: "When an agent invokes tools across five different MCP servers, the resulting log fragments live on five different file systems, with no shared schema, no shared identity, and no shared retention policy. Second, native logs are mutable. Compliance frameworks require tamper-resistant, append-only storage."
Audit Logging Architecture
(login, config, RBAC)"] Agent["Agent Actions
(execute, tool call, decision)"] LLM["LLM Calls
(model, tokens, prompt hash)"] Policy["Policy Decisions
(allow, block, redact)"] Data["Data Access
(KB read, vector search)"] Gov["Governance Events
(approval, override)"] end subgraph Pipeline["Audit Pipeline"] Scrub["Secret Redaction
(API keys, tokens)"] Sign["HMAC-SHA256 Signing
(PBKDF2-derived key)"] Chain["Integrity Chain
(prevHash linking)"] Batch["Batch Queue
(flush every 3s)"] end subgraph Storage["Immutable Storage"] Append["Append-Only Table
(INSERT only, no UPDATE/DELETE)"] Hash["SHA-256 Hash
(per-entry + chain)"] end subgraph Compliance["Compliance & SIEM"] Retention["Multi-Class Retention
(30d / 1yr / 7yr)"] SIEM["SIEM Export
(Splunk, Datadog, Sentinel)"] Export["Compliance Export
(JSON, CSV, SOC2)"] Verify["Chain Verification
(integrity check)"] end User --> Scrub Agent --> Scrub LLM --> Scrub Policy --> Scrub Data --> Scrub Gov --> Scrub Scrub --> Sign Sign --> Chain Chain --> Batch Batch --> Append Append --> Hash Hash --> Retention Hash --> SIEM Hash --> Export Hash --> Verify
Audit Log Entry Structure
| Field | Type | Description |
|---|---|---|
id |
UUID | Unique entry ID |
timestamp |
DateTime | UTC timestamp |
tenant_id |
UUID | Tenant scope |
user_id |
UUID | Acting user (null for agent) |
agent_id |
UUID | Acting agent (null for user) |
action |
String | create, update, delete, read, execute, approve, reject, override |
entity_type |
String | agent, knowledge_base, user, role, policy, webhook |
entity_id |
String | Affected entity ID |
before |
JSON | Entity snapshot before change |
after |
JSON | Entity snapshot after change |
policy_decision |
String | allowed, blocked, redacted, needs_approval, error |
model |
String | LLM model used (if applicable) |
tokens_used |
Integer | Total tokens (if LLM call) |
compliance_flags |
JSON | hipaa_relevant, gdpr_applicable, sox_relevant, pii_access |
risk_score |
Float | Calculated risk score |
integrity_hash |
String | SHA-256 hash of this entry |
prev_hash |
String | Hash of previous entry (chain) |
hmac |
String | HMAC-SHA256 signature |
Compliance Retention Requirements
| Framework | Required Retention | Use Case |
|---|---|---|
| SOC 2 Type II | 1 year | Audit window, access logging |
| ISO 27001 | 1 year | Annex A.12 logging, A.18 compliance |
| GDPR | 1 year (configurable) | Right-to-be-forgotten at retention boundary |
| EU AI Act | 7 years | Decision chain traceability (high-risk) |
| HIPAA | 6-7 years | Healthcare audit, covered entity |
| SEBI AI/ML | 5 years | Financial AI/ML (India) |
| RBI FREE-AI | 7 years | Financial AI (India) |
| SEC 17a-4 | WORM storage | Broker-dealer (Write Once Read Many) |
Implementation
import hashlib
import hmac
import json
from datetime import datetime, timezone
from uuid import uuid4
class AuditLogger:
"""Immutable, tamper-evident audit logging with HMAC-SHA256."""
def __init__(self, db, master_key: bytes):
self.db = db
# Derive audit signing key from master key
self.signing_key = hashlib.pbkdf2_hmac(
"sha256",
master_key,
b"ai-audit-hmac-v1",
iterations=600000,
dklen=32,
)
self._last_hash = None # Chain linking
async def log(self, tenant_id: str, user_id: str = None,
agent_id: str = None, action: str = "",
entity_type: str = "", entity_id: str = "",
before: dict = None, after: dict = None,
policy_decision: str = None, model: str = None,
tokens_used: int = None, compliance_flags: dict = None,
risk_score: float = None) -> dict:
"""Write an immutable audit log entry."""
entry_id = str(uuid4())
timestamp = datetime.now(timezone.utc)
# 1. Scrub secrets from before/after
before_scrubbed = self._scrub_secrets(before) if before else None
after_scrubbed = self._scrub_secrets(after) if after else None
# 2. Build entry (excluding hash fields)
entry = {
"id": entry_id,
"timestamp": timestamp.isoformat(),
"tenant_id": tenant_id,
"user_id": user_id,
"agent_id": agent_id,
"action": action,
"entity_type": entity_type,
"entity_id": entity_id,
"before": before_scrubbed,
"after": after_scrubbed,
"policy_decision": policy_decision,
"model": model,
"tokens_used": tokens_used,
"compliance_flags": compliance_flags or {},
"risk_score": risk_score,
}
# 3. Compute SHA-256 integrity hash
canonical = json.dumps(entry, sort_keys=True, separators=(",", ":"))
integrity_hash = hashlib.sha256(canonical.encode()).hexdigest()
# 4. Link to previous entry (chain)
prev_hash = self._last_hash
entry["integrity_hash"] = integrity_hash
entry["prev_hash"] = prev_hash
# 5. Compute HMAC-SHA256 signature
hmac_input = f"{integrity_hash}:{prev_hash}".encode()
hmac_signature = hmac.new(
self.signing_key, hmac_input, hashlib.sha256
).hexdigest()
entry["hmac"] = hmac_signature
# 6. Insert (append-only — no update or delete)
await self.db.insert("audit_logs", entry)
# 7. Update chain pointer
self._last_hash = integrity_hash
return {"audit_id": entry_id, "integrity_hash": integrity_hash}
async def verify_chain(self, start_date: str = None,
end_date: str = None) -> dict:
"""Verify the integrity of the audit log chain."""
query = {}
if start_date and end_date:
query["timestamp"] = {
"$gte": start_date,
"$lte": end_date,
}
entries = await self.db.find_all("audit_logs", query, sort=[("timestamp", 1)])
prev_hash = None
verified = 0
broken = []
for entry in entries:
# Reconstruct entry without hash fields
entry_data = {k: v for k, v in entry.items()
if k not in ("integrity_hash", "prev_hash", "hmac")}
canonical = json.dumps(entry_data, sort_keys=True, separators=(",", ":"))
computed_hash = hashlib.sha256(canonical.encode()).hexdigest()
# Verify hash
if computed_hash != entry["integrity_hash"]:
broken.append({
"id": entry["id"],
"issue": "hash_mismatch",
"expected": computed_hash,
"found": entry["integrity_hash"],
})
continue
# Verify chain
if entry["prev_hash"] != prev_hash:
broken.append({
"id": entry["id"],
"issue": "chain_broken",
"expected_prev": prev_hash,
"found_prev": entry["prev_hash"],
})
# Verify HMAC
hmac_input = f"{entry['integrity_hash']}:{entry['prev_hash']}".encode()
computed_hmac = hmac.new(
self.signing_key, hmac_input, hashlib.sha256
).hexdigest()
if computed_hmac != entry["hmac"]:
broken.append({
"id": entry["id"],
"issue": "hmac_mismatch",
})
prev_hash = entry["integrity_hash"]
verified += 1
return {
"total_entries": len(entries),
"verified": verified,
"broken": len(broken),
"issues": broken,
"chain_intact": len(broken) == 0,
}
def _scrub_secrets(self, data: dict) -> dict:
"""Redact secrets from audit data."""
SECRET_PATTERNS = ["api_key", "apikey", "secret", "password",
"token", "bearer", "authorization"]
scrubbed = {}
for key, value in data.items():
if any(pattern in key.lower() for pattern in SECRET_PATTERNS):
scrubbed[key] = "[REDACTED]"
elif isinstance(value, dict):
scrubbed[key] = self._scrub_secrets(value)
else:
scrubbed[key] = value
return scrubbed
async def export(self, tenant_id: str, format: str = "json",
start_date: str = None, end_date: str = None) -> str:
"""Export audit logs for compliance reporting."""
query = {"tenant_id": tenant_id}
if start_date and end_date:
query["timestamp"] = {"$gte": start_date, "$lte": end_date}
entries = await self.db.find_all("audit_logs", query,
sort=[("timestamp", 1)])
if format == "json":
return json.dumps(entries, indent=2)
elif format == "csv":
# Flatten and export as CSV
import csv
import io
output = io.StringIO()
if entries:
writer = csv.DictWriter(output, fieldnames=entries[0].keys())
writer.writeheader()
writer.writerows(entries)
return output.getvalue()
elif format == "soc2":
# SOC 2 formatted export
return json.dumps({
"framework": "SOC 2 Type II",
"tenant_id": tenant_id,
"export_date": datetime.now(timezone.utc).isoformat(),
"entry_count": len(entries),
"entries": entries,
}, indent=2)
Audit Event Types
| Category | Events | Compliance Relevance |
|---|---|---|
| User Actions | login, logout, password change, MFA enable/disable | SOC 2, ISO 27001 |
| RBAC Changes | role assigned, role revoked, permission granted/denied | SOC 2, ISO 27001 |
| Agent Actions | agent started, stopped, tool called, decision made | EU AI Act, SOC 2 |
| LLM Calls | model invoked, prompt sent, response received, tokens used | EU AI Act, GDPR |
| Policy Decisions | allowed, blocked, redacted, needs_approval, override | EU AI Act, SOC 2 |
| Data Access | KB document read, vector search, file accessed | GDPR, HIPAA |
| Governance | approval granted, approval denied, override executed | EU AI Act, SOC 2 |
| Configuration | agent config changed, KB updated, webhook added/removed | SOC 2, ISO 27001 |
| Tenant Lifecycle | tenant created, suspended, reactivated, cancelled, deleted | SOC 2, GDPR |
| Billing | plan changed, payment failed, invoice generated | SOC 2 |
AI Platform Audit Logging Checklist
- [ ] Implement append-only storage — no UPDATE or DELETE on audit table
- [ ] Enforce INSERT-only at database level (trigger or permissions)
- [ ] Sign each entry with HMAC-SHA256 using PBKDF2-derived key
- [ ] Build integrity chain — each entry links to previous via prevHash
- [ ] Store SHA-256 integrity hash per entry for independent verification
- [ ] Implement chain verification endpoint (admin only)
- [ ] Redact secrets from audit data (API keys, tokens, passwords)
- [ ] Apply secret redaction before hashing (redacted data is what's verified)
- [ ] Capture user identity: user_id, user_email, user_role, tenant_id
- [ ] Capture agent identity: agent_id, agent_version, agent_role
- [ ] Log every action: create, update, delete, read, execute, approve, reject, override
- [ ] Store before/after entity snapshots for all mutations
- [ ] Log LLM calls: model, input_tokens, output_tokens, prompt_hash, response_hash
- [ ] Log tool calls: tool_name, parameters (scrubbed), result, success/failure
- [ ] Log policy decisions: allowed, blocked, redacted, needs_approval, error
- [ ] Log data access: KB documents read, vector searches performed
- [ ] Set compliance flags: hipaa_relevant, gdpr_applicable, sox_relevant, pii_access
- [ ] Calculate risk_score per entry for anomaly detection
- [ ] Implement multi-class retention: 30 days, 1 year, 7 years
- [ ] Configure per-tenant retention based on regulatory requirements
- [ ] Audit logs must survive tenant deletion for full retention period
- [ ] Set up SIEM integration: Splunk, Datadog, Elastic, Microsoft Sentinel
- [ ] Stream audit events to SIEM in real time via webhooks
- [ ] Support OCSF (Open Cybersecurity Schema Framework) export format
- [ ] Implement JSON, CSV, and SOC 2 formatted export
- [ ] Set up CloudWatch integration for AWS-native SIEM
- [ ] Provide RESTful API for SIEM to pull audit records
- [ ] Filter audit logs by tenant_id for multi-tenant segmentation
- [ ] Implement read-only API — no create, update, delete endpoints for audit logs
- [ ] Restrict audit log access to admin role only
- [ ] Set up SIEM alerts for: mass deletions, permission changes, policy overrides
- [ ] Alert on unusual agent behavior (rapid tool calls, unexpected data access)
- [ ] Batch audit entries and flush every 3 seconds for performance
- [ ] Re-queue failed writes for next flush cycle (max 3 retries)
- [ ] Index audit table on: tenant_id, timestamp, agent_id, action
- [ ] Integrate with agentic AI governance policy decisions
- [ ] Integrate with role hierarchy RBAC changes
- [ ] Integrate with multi-tenant architecture tenant scoping
- [ ] Log tenant suspension lifecycle events
- [ ] Log BYOK key registration, rotation, revocation
- [ ] Log cost tracking quota changes and budget alerts
- [ ] Apply zero-trust architecture to audit log access
- [ ] Apply OWASP LLM Top 10 logging requirements
- [ ] Set up platform monitoring for audit system health
- [ ] Consider self-hosted AI for audit data sovereignty
- [ ] Test chain integrity: verify tamper detection on modified entries
- [ ] Test secret redaction: verify no secrets in audit logs
- [ ] Test SIEM integration: verify events arrive in SIEM
- [ ] Document audit log schema and retention policy for compliance audits
- [ ] Quarterly audit: verify chain integrity, review retention compliance
- [ ] Consider AI incident response using audit logs
FAQ
What is AI platform audit logging?
AI platform audit logging is an immutable, append-only record of every action taken across an AI platform — user actions, agent actions, LLM calls, tool calls, policy decisions, data access, and governance events. Unlike standard application logs which are mutable and operational, audit logs are compliance artifacts that must be tamper-evident, non-repudiable, and retained per regulatory requirements. Every entry captures: who (user_id, agent_id, tenant_id), what (action, entity, before/after), when (timestamp), why (reason, policy_decision), and integrity (HMAC-SHA256 hash). Audit logs support SOC 2, ISO 27001, GDPR, HIPAA, EU AI Act, and financial regulations (SEC 17a-4). They are the foundation of responsible AI deployment at scale.
How do you make audit logs tamper-evident?
Make audit logs tamper-evident with: (1) Append-only storage — no UPDATE or DELETE operations permitted, only INSERT. Database enforces this at the collection/table level. (2) HMAC-SHA256 signing — each entry is signed with a key derived from a vault master key using PBKDF2. The HMAC is computed over all fields (excluding itself) with keys sorted alphabetically for deterministic output. (3) Integrity chain — each entry includes the prevHash (hash of the previous entry), forming a blockchain-like chain. Tampering with any entry breaks the chain. (4) SHA-256 integrity hash — each entry stores its own hash for independent verification. (5) Read-only API — no create, update, or delete endpoints exist for audit logs. (6) Verification endpoint — admin can verify the entire chain integrity at any time.
What retention periods are required for AI audit logs?
AI audit log retention depends on the regulatory framework: (1) SOC 2 Type II — 1 year (audit window). (2) ISO 27001 — 1 year baseline. (3) GDPR — right-to-be-forgiven honored at retention boundary, typically 1 year. (4) EU AI Act — 7 years for decision_chain (high-risk systems). (5) HIPAA — 6 years (most uses), 7 years for strict covered-entity. (6) SEBI AI/ML — 5 years. (7) RBI FREE-AI — 7 years. (8) SEC 17a-4 — requires WORM (Write Once Read Many) storage. Implementation: multi-class retention — 30 days for debugging, 1 year for SOC 2/GDPR, 7 years for HIPAA/financial. Configure per-tenant retention based on their regulatory requirements. Audit logs must survive tenant deletion for the full retention period.
What should AI audit logs capture?
AI audit logs should capture: (1) User identity — user_id, user_email, user_role, tenant_id. (2) Agent identity — agent_id, agent_version, agent_role. (3) Action — create, update, delete, read, execute, approve, reject, override. (4) Entity — entity_type, entity_id, entity_name, before/after snapshots. (5) LLM calls — model, input_tokens, output_tokens, prompt_hash, response_hash, latency. (6) Tool calls — tool_name, parameters, result, success/failure. (7) Policy decisions — allowed, blocked, redacted, needs_approval, error. (8) Data access — what knowledge base documents were retrieved, what vector search was performed. (9) Compliance flags — hipaa_relevant, gdpr_applicable, sox_relevant, pii_access. (10) Security metrics — risk_score, query_complexity. (11) Timing — timestamp, duration_ms. (12) Integrity — HMAC-SHA256 hash, prevHash for chain.
How do you integrate AI audit logs with SIEM systems?
Integrate AI audit logs with SIEM (Splunk, Datadog, Elastic, Microsoft Sentinel) via: (1) Real-time webhook streaming — send audit events to SIEM endpoints as they occur. (2) OCSF (Open Cybersecurity Schema Framework) export — normalize audit events to OCSF format for cross-platform compatibility. (3) JSON/CSV/NDJSON export — batch export for compliance reporting and historical analysis. (4) CloudWatch integration — stream to AWS CloudWatch Logs for AWS-native SIEM. (5) API access — RESTful API for SIEM to pull audit records. (6) Per-tenant segmentation — SIEM can filter by tenant_id for multi-tenant platforms. SIEM integration transforms audit data from a compliance artifact into a real-time governance tool — enabling anomaly detection, alerting on suspicious patterns, and automated incident response. Configure SIEM alerts for: mass deletions, permission changes, policy overrides, and unusual agent behavior.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →