| name | compliance-audit |
| description | Compliance and audit-log workflow: immutable audit trail design, SOC 2 / ISO 27001 / HIPAA / PCI-DSS control mapping, access control reviews, data lineage, and compliance automation in CI/CD. Complements gdpr-privacy and security-review. |
Compliance & Audit Log
Scope: Audit trails, compliance control implementation, and automated compliance checks.
For GDPR-specific data subject rights and consent management, see gdpr-privacy.
For access control and authentication patterns, see auth-patterns and security-review.
When to Activate
- Designing an audit log for a sensitive feature (admin actions, PII access, financial transactions)
- Preparing for SOC 2 Type II audit
- Implementing HIPAA, PCI-DSS, or ISO 27001 controls
- Adding access control review / privileged action logging
- Setting up compliance checks in CI/CD
- Responding to a compliance audit request (evidence gathering)
Audit Log Design
What to log
An audit event answers: Who did what to which resource at when, and from where.
interface AuditEvent {
actor_id: string;
actor_email: string;
actor_ip: string;
actor_role: string;
action: string;
outcome: 'success' | 'failure';
failure_reason?: string;
resource_type: string;
resource_id: string;
timestamp: string;
request_id: string;
session_id: string;
service: string;
}
Action naming convention
<resource>.<verb>
user.created
user.deleted
user.password_changed
user.mfa_disabled
payment.refunded
record.exported # data export is high-value for compliance
admin.permission_granted
admin.permission_revoked
api_key.rotated
What NOT to include
- Passwords, secrets, tokens (even hashed)
- Full PII beyond what's required for audit (no SSNs in audit logs)
- Health records content (HIPAA: log access, not content)
- Payment card data (PCI: log the transaction ID, not the card number)
Immutable Audit Log Implementation
Storage requirements
| Property | Requirement |
|---|
| Immutability | Append-only; no UPDATE or DELETE on audit records |
| Retention | Minimum 1 year accessible, 7 years archived (SOC 2 / PCI) |
| Integrity | Hash chaining or WORM storage to detect tampering |
| Encryption | At rest (AES-256) and in transit (TLS 1.2+) |
| Searchability | Index by actor_id, resource_id, timestamp, action |
PostgreSQL append-only table
CREATE TABLE audit_events (
id BIGSERIAL PRIMARY KEY,
actor_id TEXT NOT NULL,
actor_email TEXT NOT NULL,
actor_ip INET NOT NULL,
actor_role TEXT NOT NULL,
action TEXT NOT NULL,
outcome TEXT NOT NULL CHECK (outcome IN ('success', 'failure')),
failure_reason TEXT,
resource_type TEXT NOT NULL,
resource_id TEXT NOT NULL,
request_id TEXT NOT NULL,
session_id TEXT NOT NULL,
service TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
metadata JSONB
);
CREATE INDEX idx_audit_actor ON audit_events(actor_id, created_at DESC);
CREATE INDEX idx_audit_resource ON audit_events(resource_type, resource_id, created_at DESC);
CREATE INDEX idx_audit_action ON audit_events(action, created_at DESC);
REVOKE UPDATE, DELETE ON audit_events FROM app_user;
Hash chaining (tamper detection)
import crypto from 'crypto';
interface AuditEventWithHash extends AuditEvent {
prev_hash: string;
hash: string;
}
function hashEvent(event: AuditEvent, prevHash: string): string {
const payload = JSON.stringify({ ...event, prev_hash: prevHash });
return crypto.createHash('sha256').update(payload).digest('hex');
}
async function verifyChain(events: AuditEventWithHash[]): Promise<boolean> {
for (let i = 1; i < events.length; i++) {
const expected = hashEvent(events[i], events[i - 1].hash);
if (expected !== events[i].hash) {
console.error(`Chain broken at event ${events[i].id}`);
;
}
}
;
}
Framework Control Mapping
SOC 2 Type II — Trust Services Criteria
| Control | What to log | Evidence |
|---|
| CC6.1 — Logical access | Login success/failure, MFA events | user.signed_in, user.mfa_failed |
| CC6.2 — Access provisioning | Permission changes | admin.permission_granted/revoked |
| CC6.3 — Access termination | Account deletion/suspension | user.suspended, user.deleted |
| CC7.2 — System monitoring | Security events, anomalies | Alert logs, SIEM integration |
| CC9.2 — Change management | Deploys, config changes | CI/CD deploy events |
| A1.1 — Availability | Uptime, SLO data | Error budget reports |
HIPAA (Healthcare)
Must log: access to Protected Health Information (PHI)
async function getPatientRecord(actorId: string, patientId: string) {
const record = await db.patients.findById(patientId);
await auditLog({
actor_id: actorId,
action: 'patient_record.viewed',
resource_type: 'patient_record',
resource_id: patientId,
outcome: 'success',
metadata: {
record_type: 'clinical_notes',
}
});
return record;
}
PCI-DSS (Payment card)
| Requirement | Log event |
|---|
| 10.2.1 — Access to cardholder data | payment.card_data_accessed |
| 10.2.2 — Admin actions | admin.* |
| 10.2.4 — Invalid access attempts | user.signed_in with outcome=failure |
| 10.2.7 — Audit log initialization | Log system startup/shutdown |
Privileged Action Review
High-risk actions requiring additional controls:
const PRIVILEGED_ACTIONS = new Set([
'admin.permission_granted',
'admin.role_changed',
'user.deleted',
'user.impersonated',
'api_key.created',
'data.bulk_exported',
'config.changed',
]);
async function requirePrivilegedActionApproval(action: string, actorId: string) {
if (!PRIVILEGED_ACTIONS.has(action)) return;
const session = await getSession(actorId);
if (!session.mfa_verified_at || Date.now() - session.mfa_verified_at > 15 * 60 * 1000) {
throw new Error('MFA re-confirmation required for privileged action');
}
const DUAL_APPROVAL_REQUIRED = new Set(['user.deleted', 'data.bulk_exported']);
if (DUAL_APPROVAL_REQUIRED.(action)) {
(actorId, action);
}
}
Compliance Automation in CI/CD
Secret scanning
jobs:
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified
dependency-audit:
steps:
- run: npm audit --audit-level=high
- run: pip-audit --requirement requirements.txt
License compliance
license-check:
steps:
- run: npx license-checker --onlyAllow "MIT;Apache-2.0;BSD-2-Clause;BSD-3-Clause;ISC"
SAST (Static Application Security Testing)
sast:
steps:
- uses: github/codeql-action/analyze@v3
with:
languages: javascript, python
Compliance Evidence Package
For an audit, collect:
scripts/compliance-report.sh \
--from 2024-01-01 \
--to 2024-12-31 \
--controls soc2-cc6,soc2-cc7 \
--output audit-evidence-2024.zip
Evidence package includes:
- User access list (current) with roles
- Access change log (who was granted/revoked what, when)
- Failed login report (with source IPs)
- Privileged action log (admin actions with approvals)
- Deploy log (what was deployed, when, by whom)
- Vulnerability scan reports
- Penetration test results (if applicable)
Related