# Account deletion cascades to AI conversation data

- **Pattern:** `ab-000169` (`ai-data-privacy.data-retention-deletion.gdpr-erasure-ai-data`)
- **Severity:** info
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000169
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000169)

## Why it matters

GDPR Article 17 (Right to Erasure) and CCPA §1798.105 require that deletion of a user's account removes all associated personal data — not just the primary user record. CWE-459 (Incomplete Cleanup) covers exactly this pattern: a delete operation that removes the primary entity but leaves related records orphaned. AI conversation history typically contains some of the most sensitive personal data in the system — verbatim user statements about their problems, circumstances, and intentions. Orphaned conversation records after account deletion mean the user's data persists in the database indefinitely under a now-deleted user ID, violating the erasure right and potentially creating ghost data that can be queried without ownership context.

## Severity rationale

Info because cascade delete configuration is a correctness issue rather than an exploitable vulnerability, but orphaned AI data after account deletion constitutes a documented GDPR Art. 17 compliance gap.

## Remediation

Configure database-level cascade deletes so conversation records are automatically removed when the parent user is deleted — no application code change required for each new deletion path.

```prisma
// schema.prisma
model Conversation {
  userId String
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```

If explicit deletion is required (e.g., audit requirements demand a logged deletion event), use a transaction in your account deletion action:

```ts
await db.$transaction([
  db.message.deleteMany({ where: { conversation: { userId } } }),
  db.conversation.deleteMany({ where: { userId } }),
  db.user.delete({ where: { id: userId } })
])
```

Verify by writing an integration test: create a user, add conversations, delete the user, and assert `db.conversation.count({ where: { userId } })` returns 0.

## Detection

- **ID:** `gdpr-erasure-ai-data`
- **Severity:** `info`
- **What to look for:** Enumerate every relevant item. Locate the account deletion flow — an API route or server action that deletes a user account. Examine what data it removes. Look for: explicit delete calls on conversation/message tables before deleting the user record, database cascade delete configuration (`onDelete: Cascade` in Prisma, `ON DELETE CASCADE` in SQL schema), or a documented gap where conversation records would become orphaned after user deletion.
- **Pass criteria:** At least 1 of the following conditions is met. Account deletion either explicitly deletes conversation/AI data records, or database foreign key cascade rules are configured to automatically delete related conversation records when the parent user is deleted.
- **Fail criteria:** Account deletion logic removes the user record but does not explicitly delete related AI conversation data, and database schema does not include cascade delete rules — leaving orphaned conversation records after the user is gone.
- **Skip (N/A) when:** No user accounts or no persisted AI conversation data. Or conversations are stored without a user foreign key (anonymous/ephemeral sessions only).
- **Detail on fail:** `"Account deletion in [file] removes the user record but conversation/message records lack cascade delete — orphaned AI data with PII may remain after account deletion"`
- **Remediation:** "Right to Erasure" means all data about a user should be removable, including AI interaction history. The cleanest way to implement this is database-level cascade deletes.

  In Prisma schema, add `onDelete: Cascade` to the relationship:

  ```prisma
  model Conversation {
    userId String
    user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
  }
  ```

  If explicit deletion is preferred over cascade:

  ```ts
  // In account deletion server action
  await db.$transaction([
    db.message.deleteMany({ where: { conversation: { userId } } }),
    db.conversation.deleteMany({ where: { userId } }),
    db.user.delete({ where: { id: userId } })
  ])
  ```

  Verify by creating a test account, adding conversations, deleting the account, and confirming conversation records are gone.

## External references

- gdpr Art. 17
- ccpa §1798.105
- cwe CWE-459

Taxons: privacy-consent, data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000169
