audit-logging
Implement tamper-evident audit logs for compliance (SOC 2, HIPAA, PCI DSS). Use when building compliance audit trails, tracking who did what and when, or implementing immutable event logs that satisfy regulatory retention requirements.
What this skill does
# Audit Logging
## Overview
Compliance audit logs must answer: **who** did **what** to **which resource**, **when**, from **where**, and with **what result**. They must also be tamper-evident — an auditor must be able to verify logs haven't been modified.
**Regulatory retention requirements:**
| Standard | Retention |
|----------|-----------|
| HIPAA | 6 years |
| PCI DSS | 12 months online + 12 months archive |
| SOC 2 (typical) | 1 year |
| GDPR | Defined by purpose (often 1-3 years) |
## Required Log Fields
```typescript
interface AuditLogEntry {
// Core required fields
id: string; // UUID — unique event identifier
timestamp: string; // ISO 8601 UTC — when the event occurred
actor_id: string; // User/service that performed the action
actor_type: 'user' | 'service' | 'admin' | 'system';
action: string; // What happened: "read" | "write" | "delete" | "login" | "export"
resource_type: string; // "patient_record" | "invoice" | "user_account"
resource_id: string; // ID of the affected resource
result: 'success' | 'failure' | 'denied';
// Context fields
ip_address: string; // Source IP
user_agent?: string; // Browser/client identifier
session_id?: string; // Session identifier
// Optional fields
changed_fields?: string[]; // For write operations: which fields changed
old_values?: Record<string, unknown>; // Previous values (be careful with PII)
reason?: string; // Clinical justification (HIPAA), business reason
// Tamper-evidence
prev_hash?: string; // Hash of previous log entry (hash chaining)
hash: string; // SHA-256 of this entry
}
```
## Hash Chain Implementation
Hash chaining makes log tampering detectable: each entry includes a hash of the previous entry. Modifying any entry breaks the chain.
```python
import json
import hashlib
import uuid
from datetime import datetime, timezone
from typing import Optional
class AuditLogger:
def __init__(self, db_connection):
self.db = db_connection
self._last_hash: Optional[str] = None
def _compute_hash(self, entry: dict, prev_hash: Optional[str]) -> str:
"""Compute SHA-256 of the log entry for tamper-evidence."""
hashable = {
"id": entry["id"],
"timestamp": entry["timestamp"],
"actor_id": entry["actor_id"],
"action": entry["action"],
"resource_type": entry["resource_type"],
"resource_id": entry["resource_id"],
"result": entry["result"],
"prev_hash": prev_hash or "GENESIS"
}
content = json.dumps(hashable, sort_keys=True)
return hashlib.sha256(content.encode()).hexdigest()
async def log(
self,
actor_id: str,
actor_type: str,
action: str,
resource_type: str,
resource_id: str,
result: str,
ip_address: str,
**kwargs
) -> dict:
"""Create and persist a tamper-evident audit log entry."""
# Get last hash for chaining
prev_hash = self._last_hash or await self._get_last_hash()
entry = {
"id": str(uuid.uuid4()),
"timestamp": datetime.now(timezone.utc).isoformat(),
"actor_id": actor_id,
"actor_type": actor_type,
"action": action,
"resource_type": resource_type,
"resource_id": resource_id,
"result": result,
"ip_address": ip_address,
"prev_hash": prev_hash or "GENESIS",
**kwargs
}
entry["hash"] = self._compute_hash(entry, prev_hash)
self._last_hash = entry["hash"]
# Persist to append-only storage
await self.db.audit_logs.insert_one(entry)
return entry
async def verify_chain(self, limit: int = 1000) -> bool:
"""Verify the hash chain integrity."""
entries = await self.db.audit_logs.find(
sort=[("timestamp", 1)],
limit=limit
)
prev_hash = "GENESIS"
for i, entry in enumerate(entries):
expected_hash = self._compute_hash(entry, prev_hash)
if entry["hash"] != expected_hash:
print(f"❌ Chain broken at entry {i}: id={entry['id']}")
return False
if entry["prev_hash"] != prev_hash:
print(f"❌ prev_hash mismatch at entry {i}")
return False
prev_hash = entry["hash"]
print(f"✅ Chain verified: {len(entries)} entries intact")
return True
async def _get_last_hash(self) -> Optional[str]:
last = await self.db.audit_logs.find_one(sort=[("timestamp", -1)])
return last["hash"] if last else None
```
## Express.js Audit Middleware
```javascript
const { v4: uuidv4 } = require('uuid');
const crypto = require('crypto');
class AuditLogger {
constructor(db) {
this.db = db;
}
async log({ actorId, actorType = 'user', action, resourceType, resourceId,
result, ipAddress, sessionId, reason, changedFields }) {
const prevHash = await this.getLastHash();
const entry = {
id: uuidv4(),
timestamp: new Date().toISOString(),
actor_id: actorId,
actor_type: actorType,
action,
resource_type: resourceType,
resource_id: resourceId,
result,
ip_address: ipAddress,
session_id: sessionId,
reason,
changed_fields: changedFields,
prev_hash: prevHash || 'GENESIS',
};
entry.hash = this.computeHash(entry);
await this.db.auditLogs.create(entry);
return entry;
}
computeHash(entry) {
const hashable = JSON.stringify({
id: entry.id,
timestamp: entry.timestamp,
actor_id: entry.actor_id,
action: entry.action,
resource_type: entry.resource_type,
resource_id: entry.resource_id,
result: entry.result,
prev_hash: entry.prev_hash,
});
return crypto.createHash('sha256').update(hashable).digest('hex');
}
async getLastHash() {
const last = await this.db.auditLogs.findOne({ order: [['timestamp', 'DESC']] });
return last?.hash || null;
}
}
// Express middleware — auto-log all requests
const createAuditMiddleware = (auditLogger) => async (req, res, next) => {
const start = Date.now();
// Capture response
const originalSend = res.send;
res.send = function(body) {
res.send = originalSend;
// Log after response sent
setImmediate(async () => {
try {
await auditLogger.log({
actorId: req.user?.id || 'anonymous',
actorType: req.user ? 'user' : 'anonymous',
action: `${req.method.toLowerCase()}_${req.route?.path || req.path}`,
resourceType: req.params.resourceType || 'api_request',
resourceId: req.params.id || req.path,
result: res.statusCode < 400 ? 'success' :
res.statusCode === 403 ? 'denied' : 'failure',
ipAddress: req.ip,
sessionId: req.session?.id,
duration_ms: Date.now() - start,
});
} catch (err) {
console.error('Audit log failed:', err);
// Never let audit logging errors break the main flow
}
});
return originalSend.apply(this, arguments);
};
next();
};
```
## PostgreSQL Append-Only Table
```sql
-- Append-only audit log table
-- The CHECK constraint and trigger prevent UPDATE/DELETE
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
actor_id TEXT NOT NULL,
actor_type TEXT NOT NULL CHECK (actor_type IN ('user', 'service', 'admin', 'system')),
action TEXT NOT NULL,
resource_type TEXT NOT NULL,
resource_id TEXT NOT NULL,
result TEXT NOT NULL CHECK (result IN ('success', 'failure', 'denied')),
ip_address INET,
session_id TEXT,
reason Related in Security
mac-ops
IncludedComprehensive macOS workstation operations — diagnose kernel panics, identify failing drives, audit launchd startup items, decode wake reasons, triage TCC permission denials, manage APFS snapshots, recover from no-boot. Use for: Mac is slow, slow bootup, won't boot, kernel panic, kernel_task hot, mds_stores CPU, photoanalysisd, cloudd, login loop, gray screen, sleep wake failure, drive failing, IO errors, APFS snapshots eating space, Time Machine local snapshots, Spotlight indexing, launchd, LaunchAgent, LaunchDaemon, login items, TCC permissions, Full Disk Access, Screen Recording denied, Gatekeeper, quarantine, com.apple.quarantine, app is damaged, helper tool, /Library/PrivilegedHelperTools, pmset, wake reasons, dark wake, sysdiagnose, panic.ips, DiagnosticReports, configuration profile, MDM profile, remote diagnostics over SSH.
a11y-audit
IncludedRun accessibility audits on web projects combining automated scanning (axe-core, Lighthouse) with WCAG 2.1 AA compliance mapping, manual check guidance, and structured reporting. Output is configurable: markdown report only, markdown plus machine-readable JSON, or markdown plus issue tracker integration. Use this skill whenever the user mentions "accessibility audit", "a11y audit", "WCAG audit", "accessibility check", "compliance scan", or asks to check a web project for accessibility issues. Also trigger when the user wants to verify WCAG conformance or map findings to a specific standard (CAN-ASC-6.2, EN 301 549, ADA/AODA).
erpclaw
IncludedAI-native ERP system with self-extending OS. Full accounting, invoicing, inventory, purchasing, tax, billing, HR, payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and financial reporting. 413 actions across 14 domains, 43 expansion modules. Constitutional guardrails, adversarial audit, schema migration. Double-entry GL, immutable audit trail, US GAAP.
assess
IncludedAssesses and rates quality 0-10 across multiple dimensions (correctness, maintainability, security, performance, testability, simplicity) with pros/cons analysis. Compares against project conventions and prior decisions from memory. Produces structured evaluation reports with actionable improvement suggestions. Use when evaluating code, designs, architectures, or comparing alternative approaches.
spring-boot-security-jwt
IncludedProvides JWT authentication and authorization patterns for Spring Boot 3.5.x covering token generation with JJWT, Bearer/cookie authentication, database/OAuth2 integration, and RBAC/permission-based access control using Spring Security 6.x. Use when implementing authentication or authorization in Spring Boot applications.
code-hardcode-audit
IncludedDetect hardcoded values, magic numbers, and leaked secrets. TRIGGERS - hardcode audit, magic numbers, PLR2004, secret scanning.