GDPR Article 17 grants EU residents the right to erasure of their personal data — and it is not optional for any system that serves EU users. Without a coded deletion path, every right-to-erasure request requires manual database surgery by an engineer, which is slow, error-prone, and leaves no audit trail. CWE-212 (Improper Removal of Sensitive Information Before Storage or Transfer) covers the broader pattern of not handling PII lifecycle correctly. CCPA §1798.105 grants California residents similar rights. An untested deletion path is nearly as risky as no deletion path: without automated verification, the cascade almost certainly misses tables, leaving PII silently intact.
High because the absence of a coded, tested deletion path means every erasure request is a manual intervention with no audit trail, making GDPR Art. 17 and CCPA §1798.105 compliance undemonstrable and costly to operationalize.
Implement a deletion service in src/lib/compliance/deletion.ts that anonymizes PII in place (preserving aggregate integrity) and writes an erasure receipt. Add at least one test that asserts PII is gone after the call:
// src/lib/compliance/deletion.ts
export async function processErasureRequest(
contactId: string,
requestedBy: string
): Promise<ErasureReceipt> {
const startedAt = new Date()
await db.contact.update({
where: { id: contactId },
data: {
email: `deleted-${contactId}@erasure.invalid`,
firstName: null,
lastName: null,
phone: null,
deletedAt: startedAt
}
})
const receipt = await db.erasureAuditLog.create({
data: { contactId, requestedBy, processedAt: startedAt, tablesAffected: ['contacts', 'consent_records'] }
})
return { receiptId: receipt.id, processedAt: startedAt }
}
// src/__tests__/erasure.test.ts
it('removes PII from contacts table', async () => {
const contact = await createTestContact({ email: 'test@example.com' })
await processErasureRequest(contact.id, 'test-runner')
const after = await db.contact.findUnique({ where: { id: contact.id } })
expect(after?.email).not.toContain('test@example.com')
})
ID: compliance-consent-engine.data-subject-rights.gdpr-deletion-path
Severity: high
What to look for: Look for a code path (API endpoint, admin action, or background job) that handles a "right to erasure" request. This is required for any system serving EU residents. The path should: accept a contact identifier, erase or anonymize all personal data associated with that contact, and produce an audit receipt. Also check for test coverage — look for test files that exercise this path and verify that the contact's data is actually gone after deletion.
Pass criteria: A deletion path exists (endpoint or service method). It is invoked through a documented process (admin panel, API, or ticket workflow). At least 1 automated test exercises the deletion path and asserts that personal data is absent after deletion. Count the test assertions that verify PII removal — at least 1 must check that email is no longer readable.
Fail criteria: No deletion path exists. Or a deletion path exists but has no test coverage — there is no automated verification that it actually removes all personal data.
Skip (N/A) when: The application provably serves no EU residents and is not subject to GDPR.
Detail on fail: "No GDPR deletion endpoint or admin action found — right to erasure requests would require manual database intervention" or "deleteContact() method exists but zero test coverage — no automated verification it cascades correctly"
Remediation: Implement a tested deletion service:
// src/lib/compliance/deletion.ts
export async function processErasureRequest(contactId: string, requestedBy: string): Promise<ErasureReceipt> {
const startedAt = new Date()
// Anonymize rather than hard-delete where aggregates depend on the row
await db.contact.update({
where: { id: contactId },
data: {
email: `deleted-${contactId}@erasure.invalid`,
firstName: null,
lastName: null,
phone: null,
deletedAt: startedAt,
erasureRequestedBy: requestedBy
}
})
// Hard-delete or anonymize consent records (retain the fact that consent was revoked, erase the PII)
await db.consentRecord.updateMany({
where: { contactId },
data: { ipAddress: null, userAgent: null }
})
// Log the erasure event
const receipt = await db.erasureAuditLog.create({
data: { contactId, requestedBy, processedAt: startedAt, tablesAffected: ['contacts', 'consent_records'] }
})
return { receiptId: receipt.id, processedAt: startedAt }
}
// test: src/__tests__/erasure.test.ts
it('erasure removes PII from contacts and consent_records', async () => {
const contact = await createTestContact({ email: 'test@example.com' })
await processErasureRequest(contact.id, 'test-runner')
const after = await db.contact.findUnique({ where: { id: contact.id } })
expect(after?.email).not.toContain('test@example.com')
})