# Data retention policy limits CDE storage to business requirements

- **Pattern:** `ab-001197` (`ecommerce-pci.monitoring-compliance.data-retention-policy`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001197
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001197)

## Why it matters

PCI-DSS 4.0 Req 3.2 limits cardholder data storage to the minimum required for business or legal needs; Req 3.2.1 requires that data retention and disposal policies are defined and implemented. Data that is never deleted accumulates indefinitely — old failed transaction records, expired tokens, and support tickets create a growing pool of sensitive data that broadens breach impact year over year. GDPR Art. 5(1)(e) (storage limitation) and NIST SI-12 both require data to be disposed of when no longer needed. Without automated purging, retention policies are aspirational rather than enforced.

## Severity rationale

Low because data accumulation expands breach impact over time but does not itself enable unauthorized access — the risk compounds gradually rather than presenting an immediate exploitable condition.

## Remediation

Create `docs/data-retention-policy.md` with specific retention periods for at least three data types, then implement automated purging via a Vercel cron route or database scheduled job. Without automation, the policy is not enforced.

```typescript
// src/app/api/cron/purge-data/route.ts
export async function POST(req: Request) {
  if (req.headers.get('authorization') !== `Bearer ${process.env.CRON_SECRET}`)
    return new Response('Unauthorized', { status: 401 });

  const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!);
  // Purge failed transactions older than 90 days (PCI Req 3.2)
  await supabase.from('failed_transactions').delete()
    .lt('created_at', new Date(Date.now() - 90 * 864e5).toISOString());
  return new Response('ok');
}
```

In `vercel.json`, schedule the route at `"cron": "0 3 * * *"`. Define transaction logs (7 years), audit logs (3 years), and failed attempts (90 days) as the minimum three retention categories.

## Detection

- **ID:** `data-retention-policy`
- **Severity:** `low`
- **What to look for:** Search for data retention documentation (look for "retention", "purge", "data-lifecycle" in `docs/` and project root). Count the number of data types with defined retention periods. For each data type, check whether a specific retention duration is stated (not "indefinite" or "as needed"). Look for automated purging code: cron jobs, scheduled functions, or database TTL configurations that delete data beyond retention periods.
- **Pass criteria:** At least 1 data retention policy document exists that defines retention periods for at least 3 data types with specific durations (e.g., "transaction logs: 7 years", "failed attempts: 90 days"). At least 1 automated purging mechanism is implemented (cron job, scheduled function, or database TTL that deletes expired data). Report: "X data types with defined retention, Y automated purging mechanisms."
- **Fail criteria:** No retention policy documented (0 files), or policy exists but covers fewer than 3 data types, or data types lack specific durations, or 0 automated purging mechanisms implemented.
- **Skip (N/A) when:** No cardholder data or payment-related data stored locally (all data handled by third-party processor, no local database storing transaction or card data).
- **Detail on fail:** Specify the gap. Example: `"No data retention policy found. 0 documentation files reference retention periods."` or `"docs/data-retention.md defines 2 data types (below 3 minimum). No automated purging code found (0 cron jobs, 0 TTL configs)."`
- **Remediation:** Create data retention policy and implement automated purging. Create `docs/data-retention-policy.md`:

  ```markdown
  # Data Retention Policy

  ## Retention Periods by Data Type

  | Data Type | Retention Period | Justification | Notes |
  |-----------|------------------|---------------|-------|
  | Transaction logs | 7 years (2555 days) | Tax/audit requirements | Immutable archive |
  | Audit logs | 3 years (1095 days) | PCI DSS requirement | Searchable database |
  | Customer communication logs | 1 year (365 days) | Customer service | Archived after 1 year |
  | Payment method tokens | Duration of relationship | Active customer card | Can be deleted on request |
  | Billing/invoice data | 7 years | Tax records | Archival storage |
  | Failed transaction attempts | 90 days | Fraud investigation | Then deleted |
  | Support tickets | 1 year | Customer support reference | Then archived |
  ```

  Implement automated purging:
  ```typescript
  // src/app/api/cron/purge-data/route.ts
  import { createClient } from '@/utils/supabase/server';

  export async function POST(req) {
    // Verify cron secret
    if (req.headers.get('authorization') !== `Bearer ${process.env.CRON_SECRET}`) {
      return new Response('Unauthorized', { status: 401 });
    }

    const supabase = await createClient();

    // Purge failed transactions older than 90 days
    await supabase
      .from('failed_transactions')
      .delete()
      .lt('created_at', new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString());

    // Purge support tickets older than 1 year
    await supabase
      .from('support_tickets')
      .update({ archived: true })
      .lt('created_at', new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString());

    // Archive audit logs older than 3 years to cold storage
    const archivedLogs = await supabase
      .from('audit_logs')
      .select('*')
      .lt('created_at', new Date(Date.now() - 1095 * 24 * 60 * 60 * 1000).toISOString())
      .limit(10000);

    if (archivedLogs.data?.length > 0) {
      // Export to S3 cold storage
      await s3.putObject({
        Bucket: 'audit-log-archive',
        Key: `archive-${new Date().toISOString().split('T')[0]}.json.gz`,
        Body: gzip(JSON.stringify(archivedLogs.data)),
      });
    }

    return new Response(JSON.stringify({ success: true, purged: true }));
  }
  ```

## External references

- pci-dss Req 3.2
- pci-dss Req 3.2.1
- gdpr Art. 5(1)(e)
- nist SI-12
- iso-27001 A.8.10

Taxons: regulatory-conformance, data-integrity

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