Audit Logging
Verify your agents log actions for accountability and compliance.
EU AI Act Article 12 requires AI systems to have automatic logging capabilities that enable tracing the system’s operation.
What is Article 12?
EU AI Act Article 12 (Record-Keeping) requires:
High-risk AI systems shall technically allow for the automatic recording of events (‘logs’) while the system is operating.
Logs must capture:
- Usage periods - When the system was in operation
- Input data - Reference database against which input was checked
- Identification of persons - Natural persons involved in verification
- Output data - For systems used for identification purposes
What Inkog Detects
Inkog’s universal_missing_audit_logging rule detects agents that perform sensitive operations without logging configured.
Detection Rule
id: universal_missing_audit_logging
title: Missing Audit Logging for Agent Actions
severity: MEDIUM
category: governance
compliance_mapping:
eu_ai_act: ["Article 12"]
nist_ai_rmf: ["GOVERN 5.2"]
iso_42001: ["8.4"]What Triggers Detection
| Pattern | Detected | Explanation |
|---|---|---|
| No logging configuration in settings | Yes | Logging not enabled |
| High-risk actions without audit trail | Yes | Sensitive ops not logged |
| Missing log destinations | Yes | No log storage configured |
| Insufficient log retention | Yes | Logs don’t meet retention requirements |
Framework Examples
Microsoft Copilot Studio
Vulnerable Pattern
settings:
authentication:
enabled: true
# VULNERABLE: No logging configuration
topics:
- id: "delete-account"
nodes:
- id: "delete"
type: "powerautomate"
# Deletion happens with no audit trailCompliant Pattern
settings:
authentication:
enabled: true
# COMPLIANT: Logging enabled
logging:
enabled: true
level: "all"
destinations:
- type: "azure-monitor"
workspace: "audit-logs"
- type: "blob-storage"
container: "agent-audit-trail"
includeUserContext: true
includeToolCalls: true
retentionDays: 365
topics:
- id: "delete-account"
nodes:
- id: "delete"
type: "powerautomate"
properties:
auditLog: true # Explicit audit logging for this actionPython (LangChain/CrewAI)
Vulnerable Pattern
from langchain.agents import AgentExecutor
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
# VULNERABLE: No callbacks or logging
)
result = agent_executor.invoke({"input": query})Compliant Pattern
import logging
import json
from datetime import datetime
from langchain.callbacks import BaseCallbackHandler
class AuditLogger(BaseCallbackHandler):
"""Callback handler for audit logging."""
def __init__(self, log_file: str):
self.logger = logging.getLogger("audit")
handler = logging.FileHandler(log_file)
handler.setFormatter(logging.Formatter(
'%(asctime)s - %(message)s'
))
self.logger.addHandler(handler)
self.logger.setLevel(logging.INFO)
def on_tool_start(self, tool, input_str, **kwargs):
self.logger.info(json.dumps({
"event": "tool_start",
"tool": tool.name,
"input": input_str,
"timestamp": datetime.utcnow().isoformat()
}))
def on_tool_end(self, output, **kwargs):
self.logger.info(json.dumps({
"event": "tool_end",
"output": str(output)[:1000], # Truncate for safety
"timestamp": datetime.utcnow().isoformat()
}))
# COMPLIANT: Audit logging enabled
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
callbacks=[AuditLogger("/var/log/agent-audit.jsonl")]
)LangGraph
Vulnerable Pattern
graph = StateGraph(AgentState)
# ... add nodes and edges ...
# VULNERABLE: No checkpointer or logging
app = graph.compile()Compliant Pattern
from langgraph.checkpoint import MemorySaver
graph = StateGraph(AgentState)
# ... add nodes and edges ...
# COMPLIANT: State persistence enables audit trail
checkpointer = MemorySaver() # Or use persistent storage
app = graph.compile(checkpointer=checkpointer)
# Each run creates an auditable trace
config = {"configurable": {"thread_id": "audit_" + str(uuid4())}}
result = app.invoke(input_data, config)Salesforce Agentforce
Vulnerable Pattern
<GenAiPlanner>
<guardrails>
<!-- VULNERABLE: No audit configuration -->
<enabled>true</enabled>
</guardrails>
</GenAiPlanner>Compliant Pattern
<GenAiPlanner>
<guardrails>
<enabled>true</enabled>
</guardrails>
<!-- COMPLIANT: Audit logging enabled -->
<auditConfiguration>
<enabled>true</enabled>
<captureAllConversations>true</captureAllConversations>
<captureToolInvocations>true</captureToolInvocations>
<retentionPeriod>365</retentionPeriod>
<destinations>
<destination>
<type>EventMonitoring</type>
<enabled>true</enabled>
</destination>
</destinations>
</auditConfiguration>
</GenAiPlanner>What to Log
For EU AI Act compliance, logs should include:
| Field | Description | Example |
|---|---|---|
timestamp | When the action occurred | 2025-12-21T14:30:00Z |
session_id | Unique conversation identifier | sess_abc123 |
user_id | Who initiated the action | user_456 |
action | What was done | delete_customer |
input | What data was provided | {"customer_id": "789"} |
output | What result was returned | "Customer deleted" |
status | Success or failure | success |
duration_ms | How long it took | 234 |
Log Storage Best Practices
1. Immutable Storage
Use append-only logs that cannot be modified:
# Write to append-only log
with open(log_file, 'a') as f:
f.write(json.dumps(audit_entry) + '\n')2. Centralized Logging
Send logs to a central system for analysis:
import structlog
logger = structlog.get_logger()
logger.info("tool_executed",
tool="delete_customer",
customer_id="789",
user_id="456"
)3. Retention Policy
Maintain logs for the required retention period:
| Regulation | Minimum Retention |
|---|---|
| EU AI Act | Not specified, but “entire lifespan” |
| GDPR | As long as processing continues |
| SOC 2 | 1 year minimum |
| PCI DSS | 1 year minimum |
4. Tamper Evidence
Use cryptographic hashing for tamper evidence:
import hashlib
def log_with_integrity(entry):
entry_json = json.dumps(entry, sort_keys=True)
entry_hash = hashlib.sha256(entry_json.encode()).hexdigest()
return {**entry, "hash": entry_hash}Compliance Evidence
Inkog generates audit logging compliance reports:
{
"article_mapping": {
"Article 12": {
"status": "PASS",
"description": "Record-Keeping",
"finding_count": 0,
"details": {
"logging_configured": true,
"destinations_configured": 2,
"retention_days": 365,
"captures_tool_calls": true
}
}
}
}