Volver a la wiki

Incident: el borrado manual de una organización no dejaba traza ni limpiaba sus copias de seguridad

Resumen

La purga automática de organizaciones (90 días) escribía SystemLog y borraba MEDIA_ROOT/backups/<id>/ desde el fix del [[incident—20260903—huey-tasks-sin-rls-bypass]]. El botón manual “Permanent delete” del admin de Django nunca pasó por esa misma lógica: hacía eligible.delete() directamente sobre el queryset. Encontrado en PROD por una carpeta huérfana backups/19/ con 4 ZIP cifrados de una organización que ya no existe en base de datos, y 0 filas de SystemLog con action="organization.purge" pese a que el botón se había usado.

Severidad: MEDIA — no hay fuga de datos entre organizaciones ni pérdida de datos vivos; el fallo es de trazabilidad (no queda registro de quién borró qué) y de higiene de disco (copias cifradas abandonadas para siempre).

Estado: corregido en el mismo commit que lo detectó — sin incidente abierto en producción ni remediación pendiente.

Cuándo

Detectado y corregido el 2026-09-06 (task #292), commit de67696224cf7b790d8f01fe70fad7eab45eaf4c (PR #510, v1.117.0).

Síntomas visibles

Causa raíz

OrganizationAdmin.action_permanent_delete (core/admin.py) llamaba eligible.delete() directamente sobre el queryset. La purga automática de 90 días (core/tasks.py::purge_deleted_organizations) sí pasaba por _purge_one_org — la función que escribe el SystemLog y borra el directorio de backups —, pero el botón manual del admin nunca se conectó a esa misma lógica. Los dos caminos hacían la misma acción destructiva (borrar una organización para siempre) por rutas de código completamente distintas, y solo uno cumplía el contrato de trazabilidad y limpieza.

Fix aplicado

Commit de67696224cf7b790d8f01fe70fad7eab45eaf4c (PR #510, v1.117.0):

Lecciones

Preventivos futuros

Cualquier acción destructiva irreversible nueva (borrado permanente, purga, CASCADE) que pueda dispararse desde más de un sitio — panel de admin, tarea periódica, comando de gestión — se implementa UNA vez en core/services/ y se llama desde todos los sitios, nunca se reimplementa. Mismo patrón ya aplicado en [[entity—core—service—backup-service]].

Véase también

Subir