GDPR Art. 5(1)(e) (storage limitation) and CCPA §1798.100(a) require that data is not retained longer than necessary for its stated purpose. A privacy policy that documents retention periods but has no automated enforcement is a compliance promise the system cannot keep — activity logs, failed login records, and temporary uploads accumulate indefinitely. ISO 27001:2022 A.8.10 and NIST SP 800-53r5 SI-12 both require defined and enforced retention schedules. Regulators treat undeclared or unenforced retention as evidence of systemic disregard for data minimization.
Low because indefinite retention causes harm only if the data is later breached or subpoenaed, but the regulatory exposure from documented-but-unenforced retention periods is direct and demonstrable.
Document retention periods in your privacy policy and create automated jobs that enforce them. Both must exist — documentation without enforcement fails GDPR Art. 5(1)(e). Add a retention cron job in src/jobs/enforceRetention.ts:
export async function enforceRetention() {
const now = new Date();
// Activity logs: 90-day retention
const logCutoff = new Date(now);
logCutoff.setDate(logCutoff.getDate() - 90);
await db.activityLog.deleteMany({ where: { createdAt: { lt: logCutoff } } });
// Failed login attempts: 30-day retention
const loginCutoff = new Date(now);
loginCutoff.setDate(loginCutoff.getDate() - 30);
await db.failedLogin.deleteMany({ where: { attemptedAt: { lt: loginCutoff } } });
// Unconfirmed temp uploads: 24-hour retention
const uploadCutoff = new Date(now);
uploadCutoff.setHours(uploadCutoff.getHours() - 24);
const tempFiles = await db.tempFile.findMany({ where: { confirmed: false, createdAt: { lt: uploadCutoff } } });
for (const f of tempFiles) {
await deleteFromStorage(f.path);
await db.tempFile.delete({ where: { id: f.id } });
}
}
Schedule this via Vercel Cron or a task queue; log each run so you can demonstrate enforcement to auditors.
ID: community-privacy-controls.account-control.retention-policy
Severity: low
What to look for: Enumerate every relevant item. Check the privacy policy for documented data retention periods (e.g., "activity logs deleted after 90 days", "payment records retained for 7 years"). Look for database jobs, cron tasks, or scheduled functions that enforce deletion (e.g., Celery tasks, Node cron jobs). Verify periods match the documented policy.
Pass criteria: At least 1 of the following conditions is met. Privacy policy clearly states retention periods for each data category. Automated jobs enforce deletion (logs, temporary data, etc.). Retention periods match policy. Deletion happens without manual intervention.
Fail criteria: Policy is silent on retention. No automated deletion jobs. Data retained indefinitely. Retention periods don't match policy.
Skip (N/A) when: Never — data retention is a compliance requirement.
Detail on fail: Example: "Privacy policy states 'activity logs deleted after 90 days' but no deletion job exists. Logs accumulate indefinitely."
Remediation: Document and enforce retention:
## Data Retention
- **Activity logs:** Deleted after 90 days
- **Payment records:** Retained for 7 years (tax compliance)
- **Failed login attempts:** Deleted after 30 days
- **Temporary upload files:** Deleted after 24 hours if not confirmed
Create automated deletion jobs:
// Cron job (e.g., Bull queue in Node.js)
scheduleJob('delete-old-logs', '0 2 * * *', async () => {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 90);
await db.activityLog.deleteMany({
where: { createdAt: { lt: cutoffDate } }
});
});
scheduleJob('delete-temp-uploads', '0 3 * * *', async () => {
const cutoffDate = new Date();
cutoffDate.setHours(cutoffDate.getHours() - 24);
const tempFiles = await db.tempFile.findMany({
where: {
confirmed: false,
createdAt: { lt: cutoffDate }
}
});
for (const file of tempFiles) {
await deleteFileFromStorage(file.path);
await db.tempFile.delete({ where: { id: file.id } });
}
});