Security

Compliance & Audit Guide

SOC 2, GDPR, HIPAA, PCI DSS implementation. Audit logging with immutable signatures, RBAC, data privacy (PII redaction), and regulatory compliance.

~30 min read 6,000 words Production-Ready

Production Playbook for Security Teams and Compliance Officers

Ensuring Claude Code plugin workflows meet SOC 2, GDPR, HIPAA, and other regulatory requirements is critical for enterprise deployments. This playbook provides audit logging implementation, compliance checklists, data privacy patterns, access controls, and security hardening procedures for AI-powered automation.

Regulatory Frameworks

Compliance Requirements Overview

Framework Scope Key Requirements Claude Code Impact
SOC 2 Type II Service organizations Security, availability, confidentiality Audit logs, access controls, encryption
GDPR EU data subjects Consent, data minimization, right to erasure Data retention, anonymization, deletion
HIPAA Healthcare data (PHI) Encryption, access logs, BAA required Self-hosted, audit trails, no cloud APIs
PCI DSS Payment card data Encryption, access controls, logging Tokenization, secure storage
ISO 27001 Information security Risk management, controls framework Security policies, incident response

Risk Assessment

interface ComplianceRisk {
  framework: 'SOC2' | 'GDPR' | 'HIPAA' | 'PCI' | 'ISO27001';
  requirement: string;
  currentState: 'compliant' | 'non-compliant' | 'partial';
  risk: 'critical' | 'high' | 'medium' | 'low';
  remediation: string;
  dueDate: Date;
}

const risks: ComplianceRisk[] = [
{
framework: 'GDPR',
requirement: 'Right to erasure (Article 17)',
currentState: 'non-compliant',
risk: 'high',
remediation: 'Implement conversation deletion API',
dueDate: new Date('2025-12-31')
},
{
framework: 'SOC2',
requirement: 'Audit logging (CC6.1)',
currentState: 'partial',
risk: 'medium',
remediation: 'Enable immutable audit logs with timestamps',
dueDate: new Date('2025-12-28')
}
];

Audit Logging

Comprehensive Audit Trail

Required Events to Log:

enum AuditEventType {
  // Authentication
  USER_LOGIN = 'user.login',
  USER_LOGOUT = 'user.logout',
  API_KEY_CREATED = 'api_key.created',
  API_KEY_REVOKED = 'api_key.revoked',

// Data Access
CONVERSATION_READ = 'conversation.read',
CONVERSATION_CREATED = 'conversation.created',
CONVERSATION_DELETED = 'conversation.deleted',
DATA_EXPORT = 'data.export',

// Configuration Changes
PLUGIN_INSTALLED = 'plugin.installed',
PLUGIN_UNINSTALLED = 'plugin.uninstalled',
SETTINGS_CHANGED = 'settings.changed',

// Security Events
AUTHENTICATION_FAILED = 'auth.failed',
AUTHORIZATION_DENIED = 'authz.denied',
ENCRYPTION_KEY_ROTATED = 'encryption.key_rotated',
SUSPICIOUS_ACTIVITY = 'security.suspicious'
}

interface AuditLog {
id: string;
timestamp: number;
eventType: AuditEventType;
userId: string;
ipAddress: string;
userAgent: string;
resource: {
type: string;
id: string;
};
action: string;
outcome: 'success' | 'failure';
metadata: Record<string, any>;
signature: string;  // HMAC for tamper detection
}

Implementation

import crypto from 'crypto';

class AuditLogger {
private readonly secretKey: string;

constructor(secretKey: string) {
this.secretKey = secretKey;
}

async log(event: Omit<AuditLog, 'id' | 'timestamp' | 'signature'>): Promise<void> {
const auditLog: AuditLog = {
id: crypto.randomUUID(),
timestamp: Date.now(),
...event,
signature: '' // Computed below
};

// Generate HMAC signature for tamper detection
const data = JSON.stringify({
id: auditLog.id,
timestamp: auditLog.timestamp,
eventType: auditLog.eventType,
userId: auditLog.userId,
resource: auditLog.resource,
action: auditLog.action,
outcome: auditLog.outcome
});

auditLog.signature = crypto
.createHmac('sha256', this.secretKey)
.update(data)
.digest('hex');

// Write to immutable log storage
await this.writeToStorage(auditLog);
}

private async writeToStorage(log: AuditLog): Promise<void> {
// Option 1: Append-only file (WORM - Write Once Read Many)
await appendFile('/var/log/audit/audit.jsonl', JSON.stringify(log) + '
');

// Option 2: PostgreSQL with audit triggers
await db.query(
'INSERT INTO audit_logs (id, timestamp, event_type, user_id, data, signature) VALUES ($1, $2, $3, $4, $5, $6)',
[log.id, log.timestamp, log.eventType, log.userId, log, log.signature]
);

// Option 3: Send to SIEM (Splunk, ELK)
await fetch('https://siem.example.com/audit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(log)
});
}

async verify(log: AuditLog): Promise<boolean> {
const data = JSON.stringify({
id: log.id,
timestamp: log.timestamp,
eventType: log.eventType,
userId: log.userId,
resource: log.resource,
action: log.action,
outcome: log.outcome
});

const expectedSignature = crypto
.createHmac('sha256', this.secretKey)
.update(data)
.digest('hex');

return log.signature === expectedSignature;
}
}

Usage Example

const auditor = new AuditLogger(process.env.AUDIT_SECRET_KEY);

// Log authentication event
await auditor.log({
eventType: AuditEventType.USER_LOGIN,
userId: 'user-123',
ipAddress: '192.168.1.100',
userAgent: 'Mozilla/5.0...',
resource: { type: 'session', id: 'session-abc' },
action: 'authenticate',
outcome: 'success',
metadata: { method: 'api_key' }
});

// Log data access
await auditor.log({
eventType: AuditEventType.CONVERSATION_READ,
userId: 'user-123',
ipAddress: '192.168.1.100',
userAgent: 'Claude Code CLI/1.0',
resource: { type: 'conversation', id: 'conv-xyz' },
action: 'read',
outcome: 'success',
metadata: { messageCount: 42 }
});

// Log deletion (GDPR right to erasure)
await auditor.log({
eventType: AuditEventType.CONVERSATION_DELETED,
userId: 'user-123',
ipAddress: '192.168.1.100',
userAgent: 'Claude Code CLI/1.0',
resource: { type: 'conversation', id: 'conv-xyz' },
action: 'delete',
outcome: 'success',
metadata: { reason: 'user_request', gdpr_article: '17' }
});

Data Privacy & Retention

GDPR Data Minimization

interface ConversationData {
  id: string;
  userId: string;
  messages: Message[];
  metadata: {
    createdAt: number;
    lastModified: number;
    pluginsUsed: string[];
  };
}

class GDPRCompliantStorage {
// Data minimization: Only store what's necessary
async storeConversation(data: ConversationData): Promise<void> {
const minimized = {
id: data.id,
userId: data.userId,  // Keep for right to access
messages: data.messages.map(m => ({
role: m.role,
content: this.redactPII(m.content),  // Remove PII
timestamp: m.timestamp
})),
metadata: {
createdAt: data.metadata.createdAt,
pluginsUsed: data.metadata.pluginsUsed
// Omit: IP addresses, user agents, detailed telemetry
}
};

await db.conversations.insert(minimized);
}

// Redact personally identifiable information
private redactPII(text: string): string {
return text
// Email addresses
.replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Z|a-z]{2,}/g, '[EMAIL_REDACTED]')
// Phone numbers
.replace(/d{3}[-.]?d{3}[-.]?d{4}/g, '[PHONE_REDACTED]')
// SSN
.replace(/d{3}-d{2}-d{4}/g, '[SSN_REDACTED]')
// Credit cards
.replace(/d{4}[-s]?d{4}[-s]?d{4}[-s]?d{4}/g, '[CC_REDACTED]');
}
}

Data Retention Policies

enum RetentionPolicy {
  CONVERSATIONS = 90,      // 90 days
  AUDIT_LOGS = 2555,       // 7 years (SOC 2 requirement)
  ANALYTICS = 365,         // 1 year
  BACKUPS = 30             // 30 days
}

class RetentionManager {
async enforceRetention(): Promise<void> {
const now = Date.now();

// Delete old conversations (GDPR: storage limitation)
const cutoff = now - (RetentionPolicy.CONVERSATIONS * 86400000);
await db.conversations.deleteMany({
lastModified: { $lt: cutoff }
});

// Archive (not delete) audit logs
const auditCutoff = now - (RetentionPolicy.AUDIT_LOGS * 86400000);
const oldLogs = await db.auditLogs.find({
timestamp: { $lt: auditCutoff }
});
await this.archiveToS3(oldLogs);
await db.auditLogs.deleteMany({
timestamp: { $lt: auditCutoff }
});

// Delete old analytics
const analyticsCutoff = now - (RetentionPolicy.ANALYTICS * 86400000);
await db.analytics.deleteMany({
timestamp: { $lt: analyticsCutoff }
});
}

private async archiveToS3(logs: any[]): Promise<void> {
const archive = JSON.stringify(logs);
await s3.putObject({
Bucket: 'audit-logs-archive',
Key: `archive-${Date.now()}.json.gz`,
Body: gzip(archive)
});
}
}

Right to Erasure (GDPR Article 17)

class GDPRErasureHandler {
  async processErasureRequest(userId: string, requestId: string): Promise<void> {
    // Log the request
    await auditor.log({
      eventType: AuditEventType.DATA_EXPORT,
      userId,
      ipAddress: 'internal',
      userAgent: 'erasure-service',
      resource: { type: 'user', id: userId },
      action: 'gdpr_erasure',
      outcome: 'success',
      metadata: { requestId, article: '17' }
    });

// Delete all user data
await db.conversations.deleteMany({ userId });
await db.analytics.deleteMany({ userId });
await db.preferences.deleteMany({ userId });

// Anonymize audit logs (can't delete due to legal requirements)
await db.auditLogs.updateMany(
{ userId },
{ $set: { userId: `ANONYMIZED_${crypto.randomUUID()}` } }
);

// Generate confirmation
await this.sendErasureConfirmation(userId, requestId);
}

private async sendErasureConfirmation(userId: string, requestId: string): Promise<void> {
// Email or other notification confirming erasure
console.log(Erasure completed for ${userId}, request ${requestId});
}
}

Access Controls

Role-Based Access Control (RBAC)

enum Role {
  ADMIN = 'admin',
  DEVELOPER = 'developer',
  AUDITOR = 'auditor',
  USER = 'user'
}

enum Permission {
CONVERSATIONS_READ = 'conversations:read',
CONVERSATIONS_WRITE = 'conversations:write',
CONVERSATIONS_DELETE = 'conversations:delete',
PLUGINS_INSTALL = 'plugins:install',
SETTINGS_MODIFY = 'settings:modify',
AUDIT_LOGS_READ = 'audit_logs:read',
USERS_MANAGE = 'users:manage'
}

const rolePermissions: Record<Role, Permission[]> = {
[Role.ADMIN]: [
Permission.CONVERSATIONS_READ,
Permission.CONVERSATIONS_WRITE,
Permission.CONVERSATIONS_DELETE,
Permission.PLUGINS_INSTALL,
Permission.SETTINGS_MODIFY,
Permission.AUDIT_LOGS_READ,
Permission.USERS_MANAGE
],
[Role.DEVELOPER]: [
Permission.CONVERSATIONS_READ,
Permission.CONVERSATIONS_WRITE,
Permission.PLUGINS_INSTALL
],
[Role.AUDITOR]: [
Permission.AUDIT_LOGS_READ,
Permission.CONVERSATIONS_READ
],
[Role.USER]: [
Permission.CONVERSATIONS_READ,
Permission.CONVERSATIONS_WRITE
]
};

class AccessControl {
hasPermission(userRole: Role, permission: Permission): boolean {
return rolePermissions[userRole].includes(permission);
}

async enforcePermission(
userId: string,
permission: Permission,
action: () => Promise<void>
): Promise<void> {
const user = await db.users.findOne({ id: userId });

if (!this.hasPermission(user.role, permission)) {
await auditor.log({
eventType: AuditEventType.AUTHORIZATION_DENIED,
userId,
ipAddress: '0.0.0.0',
userAgent: 'internal',
resource: { type: 'permission', id: permission },
action: 'check',
outcome: 'failure',
metadata: { role: user.role }
});

throw new Error(`Permission denied: ${permission}`);
}

await action();
}
}

SOC 2 Compliance

SOC 2 Type II Common Criteria

CC6: Logical and Physical Access Controls

// CC6.1: Access granted based on job function
class SOC2AccessControl {
  async grantAccess(userId: string, role: Role): Promise<void> {
    // Verify user identity
    const user = await this.verifyIdentity(userId);

// Apply least privilege
const permissions = rolePermissions[role];

// Log access grant
await auditor.log({
eventType: AuditEventType.SETTINGS_CHANGED,
userId: 'system',
ipAddress: 'internal',
userAgent: 'access-control',
resource: { type: 'user', id: userId },
action: 'grant_access',
outcome: 'success',
metadata: { role, permissions }
});
}

// CC6.2: Access removed when no longer needed
async revokeAccess(userId: string): Promise<void> {
await db.users.update(
{ id: userId },
{ $set: { status: 'inactive', accessRevoked: Date.now() } }
);

await auditor.log({
eventType: AuditEventType.SETTINGS_CHANGED,
userId: 'system',
ipAddress: 'internal',
userAgent: 'access-control',
resource: { type: 'user', id: userId },
action: 'revoke_access',
outcome: 'success',
metadata: { reason: 'employment_termination' }
});
}

// CC6.7: Detection and response to security incidents
async detectSuspiciousActivity(userId: string): Promise<void> {
const recentLogins = await db.auditLogs.find({
userId,
eventType: AuditEventType.USER_LOGIN,
timestamp: { $gte: Date.now() - 3600000 } // Last hour
});

// Multiple failed logins
const failedLogins = recentLogins.filter(l => l.outcome === 'failure');
if (failedLogins.length >= 5) {
await this.lockAccount(userId);
await this.alertSecurityTeam(userId, 'brute_force_detected');
}

// Login from unusual location
const locations = recentLogins.map(l => l.ipAddress);
if (new Set(locations).size > 3) {
await this.alertSecurityTeam(userId, 'multiple_locations');
}
}

private async lockAccount(userId: string): Promise<void> {
await db.users.update(
{ id: userId },
{ $set: { locked: true, lockedReason: 'suspicious_activity' } }
);

await auditor.log({
eventType: AuditEventType.SUSPICIOUS_ACTIVITY,
userId: 'system',
ipAddress: 'internal',
userAgent: 'security-monitor',
resource: { type: 'user', id: userId },
action: 'lock_account',
outcome: 'success',
metadata: { reason: 'brute_force_detected' }
});
}

private async alertSecurityTeam(userId: string, reason: string): Promise<void> {
// Send to PagerDuty, Slack, email, etc.
console.log(`SECURITY ALERT: ${reason} for user ${userId}`);
}

private async verifyIdentity(userId: string): Promise<any> {
// Multi-factor authentication check
return await db.users.findOne({ id: userId });
}
}

SOC 2 Evidence Collection

#!/bin/bash
# collect-soc2-evidence.sh - Automated evidence gathering

EVIDENCE_DIR="/compliance/soc2/evidence-$(date +%Y-%m-%d)"
mkdir -p $EVIDENCE_DIR

# 1. Access control logs (CC6.1, CC6.2)
pg_dump -t audit_logs --data-only > $EVIDENCE_DIR/access_logs.sql

# 2. Configuration changes (CC7.2)
git log --since="30 days ago" --pretty=format:"%h %an %ad %s" > $EVIDENCE_DIR/config_changes.txt

# 3. Backup verification (A1.2)
ls -lh /backups/postgres/ > $EVIDENCE_DIR/backup_verification.txt

# 4. Encryption status (CC6.6)
openssl s_client -connect claude.example.com:443 < /dev/null | openssl x509 -text > $EVIDENCE_DIR/ssl_certificate.txt

# 5. Vulnerability scans (CC7.1)
docker scan ollama:latest > $EVIDENCE_DIR/vulnerability_scan.txt

echo "Evidence collected: $EVIDENCE_DIR"

GDPR Compliance

GDPR Articles Implementation

Article Requirement Implementation
Article 6 Lawful basis for processing Explicit consent on signup
Article 13 Information to be provided Privacy policy displayed
Article 15 Right of access /api/data-export endpoint
Article 16 Right to rectification User can edit profile
Article 17 Right to erasure /api/delete-account endpoint
Article 20 Right to data portability Export in JSON format
Article 32 Security of processing Encryption at rest and in transit

Data Subject Rights API

class GDPRDataSubjectRights {
  // Article 15: Right of access
  async exportUserData(userId: string): Promise<any> {
    const data = {
      profile: await db.users.findOne({ id: userId }),
      conversations: await db.conversations.find({ userId }),
      analytics: await db.analytics.find({ userId }),
      auditLogs: await db.auditLogs.find({ userId }).limit(100) // Last 100 entries
    };

await auditor.log({
eventType: AuditEventType.DATA_EXPORT,
userId,
ipAddress: 'internal',
userAgent: 'gdpr-export-service',
resource: { type: 'user', id: userId },
action: 'export_data',
outcome: 'success',
metadata: { gdpr_article: '15' }
});

return data;
}

// Article 17: Right to erasure
async deleteUserData(userId: string): Promise<void> {
const erasure = new GDPRErasureHandler();
await erasure.processErasureRequest(userId, crypto.randomUUID());
}

// Article 20: Right to data portability
async exportPortableData(userId: string): Promise<string> {
const data = await this.exportUserData(userId);

// Export in machine-readable format (JSON)
return JSON.stringify(data, null, 2);
}
}

HIPAA Compliance

HIPAA Technical Safeguards

PHI (Protected Health Information) must never be sent to cloud APIs:

class HIPAACompliantLLM {
  async processHealthcareData(patientData: any): Promise<string> {
    // ❌ HIPAA VIOLATION: Sending PHI to cloud
    // const response = await anthropic.messages.create({
    //   model: 'claude-3-5-sonnet-20241022',
    //   messages: [{ role: 'user', content: `Analyze: ${patientData}` }]
    // });

// ✅ HIPAA COMPLIANT: Self-hosted Ollama
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
body: JSON.stringify({
model: 'llama3.3:70b',
prompt: Analyze patient data: ${patientData},
stream: false
})
});

const result = await response.json();

// Audit log (required by HIPAA)
await auditor.log({
eventType: AuditEventType.CONVERSATION_CREATED,
userId: 'healthcare-worker-123',
ipAddress: '10.0.0.5',
userAgent: 'medical-app/1.0',
resource: { type: 'patient_analysis', id: 'patient-456' },
action: 'process_phi',
outcome: 'success',
metadata: { model: 'llama3.3:70b', local: true }
});

return result.response;
}
}

HIPAA Business Associate Agreement (BAA)

Self-hosted = No BAA required (data never leaves your control)

Required Security Controls:

# Encryption at rest
cryptsetup luksFormat /dev/sdb
cryptsetup luksOpen /dev/sdb encrypted_volume
mkfs.ext4 /dev/mapper/encrypted_volume

# Encryption in transit (TLS 1.3 only)
openssl s_server -accept 443 -cert server.crt -key server.key -tls1_3 -cipher TLS_AES_256_GCM_SHA384

# Access logging (required by HIPAA)
tail -f /var/log/audit/audit.log | grep PHI_ACCESS

Security Hardening

Security Checklist

<h2>Infrastructure Security</h2>
  • [ ] All services run with least privilege (non-root users)
  • [ ] Firewall configured (UFW/iptables) with default-deny
  • [ ] SSH access restricted (key-only, no password auth)
  • [ ] Automatic security updates enabled
  • [ ] Intrusion detection system deployed (OSSEC, Fail2ban)
<h2>Application Security</h2>
  • [ ] Input validation on all endpoints
  • [ ] SQL injection prevention (parameterized queries)
  • [ ] XSS prevention (output encoding)
  • [ ] CSRF tokens on state-changing requests
  • [ ] Rate limiting on APIs (429 responses)
<h2>Data Security</h2>
  • [ ] Encryption at rest (LUKS for disks)
  • [ ] Encryption in transit (TLS 1.3)
  • [ ] Database credentials rotated quarterly
  • [ ] API keys rotated after employee departure
  • [ ] Backups encrypted with separate key
<h2>Monitoring & Response</h2>
  • [ ] SIEM configured (Splunk, ELK)
  • [ ] Intrusion alerts sent to security team
  • [ ] Vulnerability scanning automated (weekly)
  • [ ] Penetration testing scheduled (annual)
  • [ ] Incident response plan documented

Best Practices

DO ✅

  • Log all security events
await auditor.log({
     eventType: AuditEventType.AUTHENTICATION_FAILED,
     userId: 'unknown',
     ipAddress,
     // ... full details
   });
  • Encrypt sensitive data
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from(data));
  • Implement data retention
const retention = new RetentionManager();
   setInterval(() => retention.enforceRetention(), 86400000); // Daily

DON'T ❌

  • Don't store PII unnecessarily
// ❌ Store full conversation with PII
   await db.save({ userId, messages: fullConversation });

// ✅ Redact PII before storage
await db.save({ userId, messages: redacted });
  • Don't skip audit logging
// ❌ No audit trail
   await db.conversations.delete({ id });

// ✅ Log deletion
await auditor.log({ eventType: 'conversation.deleted', ... });
await db.conversations.delete({ id });

Tools & Resources

Compliance Automation

  • Vanta: SOC 2 automation
  • Drata: Continuous compliance
  • OneTrust: GDPR automation
  • TrustArc: Privacy management

Security Scanning

  • Nessus: Vulnerability scanning
  • OWASP ZAP: Web app security testing
  • Trivy: Container scanning
  • Snyk: Dependency scanning

Summary

Key Takeaways:

  • Audit everything - Immutable logs with HMAC signatures
  • Minimize data - GDPR requires storage limitation
  • Self-host for HIPAA - Cloud APIs violate PHI rules
  • Implement RBAC - Least privilege access
  • Automate compliance - Evidence collection scripts
  • Encrypt data - At rest and in transit
  • Test regularly - Penetration tests, vulnerability scans

Compliance Checklist:

  • [ ] Audit logging implemented (immutable, signed)
  • [ ] Data retention policies enforced (90-day conversations)
  • [ ] GDPR rights endpoints deployed (access, erasure, portability)
  • [ ] RBAC configured (admin, developer, auditor, user roles)
  • [ ] SOC 2 evidence collection automated
  • [ ] HIPAA PHI never sent to cloud (self-hosted Ollama only)
  • [ ] Encryption enabled (TLS 1.3, LUKS disk encryption)
  • [ ] Security hardening complete (firewall, SSH, updates)
  • [ ] Quarterly security reviews scheduled
  • [ ] Annual penetration testing planned

Last Updated: 2025-12-24

Author: Jeremy Longshore

Related Playbooks: Self-Hosted Stack Setup, MCP Server Reliability