GDPR Article 30(1) requires records of processing activities to be maintained — without a defined retention window, these records will either grow unbounded (prompting an engineer to delete them without proper review) or get purged on an arbitrary schedule shorter than regulatory requirements. GDPR Article 5(2) accountability and CCPA §1798.185(a)(8) both require that records demonstrating compliance be available for regulatory inspection. Common guidance across CAN-SPAM, GDPR, and state privacy laws converges on 3-5 years for consent and processing records. A 90-day purge window — common in log management systems — falls catastrophically short of this.
Medium because incorrect retention periods create a gap between when records are purged and when a regulatory inquiry or litigation hold might require them, leaving the business unable to demonstrate compliance for historical processing activity.
Define retention constants explicitly in code and use them in all purge jobs — never hardcode day counts at the call site:
// src/lib/compliance/constants.ts
export const AUDIT_LOG_RETENTION_DAYS = 1825 // 5 years (conservative)
export const CONSENT_RECORD_RETENTION_DAYS = 1825
// src/lib/compliance/purge.ts — runs as a monthly cron job
import { AUDIT_LOG_RETENTION_DAYS } from './constants'
export async function purgeExpiredAuditLogs() {
const cutoff = new Date()
cutoff.setDate(cutoff.getDate() - AUDIT_LOG_RETENTION_DAYS)
const { count } = await db.auditLog.deleteMany({
where: { createdAt: { lt: cutoff } }
})
await logger.info('audit-log-purge', { deletedCount: count, cutoff })
}
Add a code comment on AUDIT_LOG_RETENTION_DAYS explaining the regulatory basis so future engineers understand why this constant cannot be reduced without a legal review.
ID: compliance-consent-engine.compliance-audit-trail.log-retention
Severity: medium
What to look for: CAN-SPAM, GDPR, and CCPA all imply (and some state privacy laws explicitly require) that you retain records of consent and processing activities for a minimum period. Common guidance is 3-5 years for consent records and processing logs. Check for: a data retention policy in code or documentation, a scheduled job that archives or purges old records, and whether purge windows are set shorter than regulatory minimums.
Pass criteria: Audit log retention is explicitly configured (in code, cron, or infrastructure) for at least 3 years. Purge jobs (if any) respect the retention window and do not delete records within the minimum period. The retention period is documented. Quote the actual retention constant or configuration value found in the codebase. Count all retention-related constants and enumerate each.
Fail criteria: No retention policy exists. Logs are purged on an arbitrary schedule shorter than 3 years (fewer than 1095 days). Or the audit log table has no retention consideration at all (will grow unbounded, making it likely to be "cleaned up" without proper consideration).
Skip (N/A) when: The application has no audit log to retain.
Detail on fail: "No audit log retention policy found — logs will grow indefinitely or be deleted on arbitrary schedule" or "Cron job purges audit_log rows older than 90 days — well short of 3-year minimum"
Remediation: Define retention explicitly:
// Retention constants — change here to update everywhere
const AUDIT_LOG_RETENTION_DAYS = 1825 // 5 years (conservative)
const CONSENT_RECORD_RETENTION_DAYS = 1825
// Purge job (runs monthly) — deletes only records older than retention window
export async function purgeExpiredAuditLogs() {
const cutoff = new Date()
cutoff.setDate(cutoff.getDate() - AUDIT_LOG_RETENTION_DAYS)
const deleted = await db.auditLog.deleteMany({
where: { createdAt: { lt: cutoff } }
})
await logger.info('audit-log-purge', { deletedCount: deleted.count, cutoff })
}