Tema
Procedimiento de Retención y Eliminación Segura de Datos
Documento: DOC-DATA-RET-01 Versión: 1.1 (2026-08-07 — retention table explícita + status implementación real) Fecha de aprobación: 2026-07-29 Owner: Security Lead + DPO Frecuencia de revisión: Anual + tras cambios significativos en el CDE Referencias PCI DSS: Req 3.2.1, Req 3.3.1, Req 9.4 Cuestionario ControlCase: Pregunta 25
0. Tabla explícita de retención (autoritativa para QSA)
Esta tabla es la fuente única de verdad de tiempos de retención por tipo de dato. Todo componente automatizado (cron, lifecycle policy, sweeper) debe cumplir estos valores.
| # | Tipo de dato | Ubicación primaria | Tiempo de retención (explícito) | Trigger de eliminación | Mecanismo | Estado |
|---|---|---|---|---|---|---|
| 1 | PAN cifrado (envelope AES-256-GCM) | Postgres card_vault.stored_cards | 30 días desde deleted_at post-soft-delete | Cron diario 03:00 UTC | PanPurgeService (NestJS @Cron) + VACUUM FULL | ✅ IMPLEMENTADO (evidencia logs 6 días consecutivos) |
| 2 | Nombre del portador (cifrado) | Postgres card_vault.stored_cards.cardholder_name_ciphertext | Igual que PAN (30 días post soft-delete) | Mismo cron que PAN | PanPurgeService (misma tabla) | ✅ IMPLEMENTADO |
| 3 | Fecha de expiración (cifrada) | Postgres card_vault.stored_cards.exp_*_ciphertext | Igual que PAN | Mismo cron | Mismo | ✅ IMPLEMENTADO |
| 4 | PAN fingerprint (HMAC-SHA256) | Postgres card_vault.stored_cards.pan_fingerprint | Igual que PAN (misma fila) | Mismo cron | Mismo | ✅ IMPLEMENTADO |
| 5 | PAN truncado (BIN + last4) | Postgres payments_core.payment_intents.card_display | 7 años (retención fiscal Colombia — Estatuto Tributario Art. 632) | Job anual (Q4) | Manual + auditado | 📋 Programado 2026-Q4 |
| 6 | CVV / CVV2 (SAD) | Vault kv-v2 secret/sad/* (RAM del proceso) | Max 15 minutos (TTL Vault kv-v2) + purge inmediato post-auth | (a) purgeAfterAuth post-response del processor · (b) Sweeper cada 60s | SadPurgeService.purgeAfterAuth + sweeper | ✅ IMPLEMENTADO (post-auth) · ⚠️ Sweeper: bug de policy corregido 2026-08-07 (SEC-042) |
| 7 | PIN / PIN Block | N/A — NO recibido | N/A | N/A | N/A | 🚫 No aplica (card-not-present only) |
| 8 | Track 1 / Track 2 | N/A — NO recibido | N/A | N/A | N/A | 🚫 No aplica (no card-present) |
| 9 | Backups de Postgres (GPG encrypted) | Backblaze B2 s3://fintrix-dr-postgres/daily/ | 7 años (bucket Object Lock Compliance) | Bucket lifecycle policy | B2 Object Lock Compliance (immutable) | ✅ IMPLEMENTADO (CronJob pg-dump-b2 diario) |
| 10 | Snapshots de Vault Raft | Backblaze B2 s3://fintrix-dr-vault/ | 7 años (Object Lock Compliance) | Bucket lifecycle | Systemd timer vault-snapshot-b2.timer + Object Lock | ✅ IMPLEMENTADO |
| 11 | Vault audit log (append-only) | Backblaze B2 s3://fintrix-dr-vault-audit/ | 3 años (Object Lock Compliance) | Bucket lifecycle | vault-audit-uploader.sh + Object Lock | ✅ IMPLEMENTADO |
| 12 | Logs de aplicación PCI (Wazuh SIEM) | Wazuh cluster + B2 archive | 1 año (PCI DSS Req 10.5.1 mínimo) — 90 días hot + 275 días cold | Wazuh retention config + B2 lifecycle | Wazuh + B2 | ✅ IMPLEMENTADO |
| 13 | pgAudit logs (DB events) | Postgres + B2 | 1 año hot / 3 años cold | pg-audit-log-shipper (daily) + B2 | Systemd timer + B2 | ✅ IMPLEMENTADO |
| 14 | K8s state (etcd snapshots) | B2 s3://fintrix-dr-k8s-state/ | 90 días | CronJob k8s-state-b2 diario | K8s CronJob | ✅ IMPLEMENTADO |
| 15 | JWT signing keys (RS256) | Vault secret/jwt/* | Rotación 6 meses | Rotación programada | Vault + K8s deployment restart | 📋 Rotación pendiente 2026-Q4 |
| 16 | API keys de merchant (SHA-256 hash) | Postgres payments_core.api_keys | Vida activa del API key | Merchant revoca desde dashboard | UPDATE revoked_at + DELETE 30d después | 📋 Endpoint activo, sin API keys creadas aún |
| 17 | Email/nombre de merchant (usuarios auth) | Postgres auth_db.users | Vida de cuenta + 30 días post-baja | Job manual + trigger | Manual auditado | ✅ IMPLEMENTADO |
| 18 | Sesiones JWT (in-memory) | RAM (JWT stateless) | 1 hora TTL | Expiración natural JWT | N/A (no storage) | ✅ IMPLEMENTADO |
| 19 | Media físico decomisionado | N/A (no on-prem — solo cloud managed) | N/A — DO garantiza wipe post-destroy | DO managed | DO NIST 800-88 (contractual) | ✅ IMPLEMENTADO (VND-2026-005 attest) |
Notas críticas:
- Todos los tiempos son absolutos, no "aproximados". Cumplimiento verificable con
SELECT max(now() - created_at) FROM <tabla>. - Cambios a esta tabla requieren aprobación CTO + DPO (bitácora en §9).
- El campo "Estado" refleja la implementación real verificada al 2026-08-07 con evidencia en
qsa-responses/EVD-Q25-EXECUTION-EVIDENCE.
1. Propósito
Definir el procedimiento operativo y automatizado para (a) mantener la retención de datos protegidos al mínimo necesario y (b) eliminarlos de forma segura al final de su ciclo de vida.
2. Alcance
Aplica a todos los datos clasificados en COVERED-INFORMATION-DATA-MATRIX.md, incluyendo:
- CHD (PAN, cardholder name, exp) almacenado en
card_vault.stored_cards - SAD (CVV) en RAM del
tokenization-service - Backups de Postgres
- Logs de aplicación e infraestructura
- Snapshots de Vault
- Media físico decomisionado
3. Principios
- Mínima retención: solo se retiene el dato mientras exista justificación de negocio activa
- Purge irreversible: eliminación cripto o físicamente irreversible (no soft delete)
- Auditabilidad: toda ejecución del purge genera evento en audit log inmutable
- Verificabilidad: el QSA puede reproducir el conteo de datos borrados en cualquier momento
4. Retenciones definidas
Ver COVERED-INFORMATION-DATA-MATRIX.md §7 para la tabla autoritativa.
5. Procedimientos de eliminación
5.1 SAD (CVV) — purge continuo
Frecuencia: cada 60 segundos + inmediato post-autorización.
Mecanismo dual:
Immediate purge post-authorization
tokenization-servicerecibe respuesta del payment processor- Independiente del resultado (aprobada/declinada/error) → invalida
tok_cvv_*inmediatamente - Emite evento Kafka
fintrix.security.sad.purgedcontoken_id,reason=post_auth,timestamp
Cron sweeper (safety net)
- Cronjob
sad-purge-sweepercorre cada 60 seg - Query Vault kv-v2: lista tokens
tok_cvv_*con lease > 15 min - Ejecuta
vault kv delete secret/data/sad/{token_id} - Emite evento Kafka
fintrix.security.sad.purgedconreason=ttl_sweep
- Cronjob
Ubicación del código: backend/apps/tokenization-service/src/sad-purge/sad-purge.service.ts — SEC-019 ✅ IMPLEMENTADO 2026-07-30 (verificado en producción con pods tokenization-service-* en namespace pci-cde).
Evidencia auditable:
- Vault audit log: entries
type=revoke path=sad/* - Kafka topic
fintrix.security.sad.purgedcon retention 7 años - Dashboard Grafana
SAD Purge Metricscon eventos/hora
5.2 PAN de tarjetas canceladas — purge diario
Frecuencia: diaria, 03:00 UTC (baja demanda).
Trigger: una tarjeta pasa a deleted_at IS NOT NULL cuando:
- El cliente elimina el payment method desde el dashboard
- La suscripción asociada se cancela y no queda otra suscripción activa con esa tarjeta
- El fraud team marca la tarjeta como comprometida
Ventana de gracia: 30 días desde deleted_at (para chargeback disputes).
Cronjob: pan-purge-canceled-cards
sql
-- Ejecutado por el cronjob (audit-logged):
BEGIN;
DELETE FROM card_vault.stored_cards
WHERE deleted_at IS NOT NULL
AND deleted_at < now() - interval '30 days'
RETURNING id, deleted_at;
-- rows returned se loguean con SHA-256 hash antes de commit
COMMIT;
-- Post-commit:
VACUUM FULL card_vault.stored_cards;
-- Fuerza reescritura del tablespace → dato en filesystem se sobrescribeUbicación del código: backend/apps/card-vault-service/src/purge/pan-purge.service.ts — SEC-020 ✅ IMPLEMENTADO 2026-07-30 (verificado con 6 días consecutivos de ejecución en producción, ver EVD-Q25-EXECUTION-EVIDENCE).
Evidencia auditable:
- Postgres audit log (pgAudit extension) con
DELETEstatements + row counts - Kafka topic
fintrix.security.pan.purgedconcard_id_hash,purged_at,days_since_deletion - Job artifact en Kubernetes:
kubectl -n pci-cde logs job/pan-purge-canceled-cards-<date>
5.3 Backups >90 días
Frecuencia: automática vía DO Spaces object lifecycle policy.
Policy configurada en bucket fintrix-db-backup-nyc3:
json
{
"Rules": [
{
"ID": "PurgeBackupsOlderThan90Days",
"Status": "Enabled",
"Filter": { "Prefix": "postgres/" },
"Expiration": { "Days": 90 }
}
]
}Verificación:
bash
# Listar backups actuales con edad
s3cmd ls s3://fintrix-db-backup-nyc3/postgres/ | awk '{print $1, $NF}'
# Alerta si se encuentra archivo > 90 días5.4 Logs >365 días
Frecuencia: automática vía Loki retention + Spaces lifecycle.
- Loki:
retention_period: 2160h(90 días online) - DO Spaces archive: lifecycle policy con expiration a 275 días adicionales
- Total: 365 días máximo, cumple PCI DSS Req 10.5.1
5.5 Media físico decomisionado
Aplica si un droplet, disco físico, o dispositivo sale del CDE.
Procedimiento (MEDIA-DISPOSAL-RUNBOOK.md):
- Backup pre-decomiso (si aún se necesita el dato)
- Wipe con
shred -uvfz -n 3 /dev/sdXo equivalente - Overwrite verificable con patterns aleatorios (mínimo 3 pases)
- Para SSD:
blkdiscard+ verificación de erase conhdparm --security-erase - Destrucción física del disco (drill o triturado industrial) por vendor certificado
- Certificate of Destruction firmado por el vendor
- Archivar CoD en drive legal + evento
fintrix.security.media.destroyeda SIEM
Vendor autorizado en Colombia: [TO_DEFINE] (Iron Mountain o similar)
5.6 Vault snapshots
Retención: 30 días de snapshots diarios + 12 snapshots mensuales (para RTO/RPO).
Cronjob en el cluster Vault:
bash
# Diario 04:00 UTC
vault operator raft snapshot save /var/backups/vault-$(date +%Y%m%d).snap
# Upload a Spaces WORM (immutable)
s3cmd put /var/backups/vault-*.snap s3://fintrix-vault-audit-nyc3/snapshots/
# Purge locales > 7 días
find /var/backups/vault-*.snap -mtime +7 -delete
# Spaces lifecycle purga > 30 días automáticamente6. Verificación de ejecución (evidencia auditable)
Todo purge debe generar evidencia verificable por el QSA. Las evidencias se archivan en:
| Purge | Evidencia primaria | Ubicación | Retención evidencia |
|---|---|---|---|
| SAD sweeper | Vault audit log entries | s3://fintrix-vault-audit-nyc3/audit/ (WORM) | 3 años |
| PAN canceled | Postgres pgAudit + Kafka event | Loki + Kafka topic | 7 años |
| Backup lifecycle | Spaces access logs | s3://fintrix-logs-archive-nyc3/spaces-access/ | 1 año |
| Logs retention | Loki retention metrics | Prometheus loki_ingester_streams_removed_total | 1 año |
| Media disposal | Certificate of Destruction (PDF) | Drive legal + escrow notarial | Vida del sistema |
| Vault snapshots | S3 access log + snapshot list | Spaces WORM | 3 años |
7. Roles y responsabilidades
| Rol | Responsabilidad |
|---|---|
| Security Lead | Aprobar cambios al procedimiento, revisar evidencia mensualmente |
| DPO | Verificar cumplimiento GDPR/Ley 1581 (retención = minimización) |
| DevOps | Mantener cronjobs, monitorear alerts de purge fallido |
| DBA | Ejecutar VACUUM FULL, verificar libertad de dead tuples |
| QSA (externo) | Verificar evidencia trimestralmente y en assessment anual |
8. Métricas y alertas
Dashboard Grafana: PCI Data Retention Compliance
| Métrica | Alerta | Escalación |
|---|---|---|
sad_tokens_older_than_15_min | > 0 durante > 5 min | PagerDuty → Security oncall |
pan_purge_job_last_success_timestamp | > 26 h ago | PagerDuty → DevOps oncall |
backup_older_than_90_days_count | > 0 | Email → DevOps |
logs_older_than_365_days_count | > 0 | Email → DevOps |
vault_snapshot_last_uploaded_timestamp | > 26 h ago | PagerDuty → Security |
9. Runbook para failed purge
Si un purge falla (ej: constraint violation, deadlock, Vault sealed):
- Alerta dispara → oncall recibe
- Oncall revisa logs del cronjob:
kubectl -n pci-cde logs job/{name} - Diagnóstico común:
Vault sealed→ escalar a custodios para unseal ceremonydeadlock detected→ identificar tx en conflicto + retry manualpg connection refused→ verificar salud del Postgres primary
- Ejecutar purge manual con audit trail:bash
kubectl -n pci-cde create job --from=cronjob/pan-purge-canceled-cards \ pan-purge-manual-$(date +%Y%m%d-%H%M) - Documentar incidente en
INCIDENTS/YYYY-MM-DD-purge-failure.md - Post-mortem obligatorio si downtime > 24 h
10. Testing anual
Cada Q4 (previo a la re-certificación):
- Test de purge: insertar 100 registros dummy con
deleted_at = now() - interval '31 days', ejecutar cronjob manual, verificar 0 rows post-ejecución - Test de restauración de backup: restaurar snapshot de hace 30 días a cluster de staging, verificar integridad
- Test de recuperación Vault: unseal ceremony completa desde snapshot de Spaces
- Test de shred: wipe de disco de prueba + intento de recovery con
photorec— debe fallar
Resultados archivados en docs/pci-dss/annual-tests/YYYY/.
11. Aprobaciones
| Rol | Nombre | Firma | Fecha |
|---|---|---|---|
| CTO | [TO_FILL] | ||
| Security Lead | [TO_FILL] | ||
| DPO | [TO_FILL] |
