Decisiones de la Auditoría Suprema s105 (Blueprints/Auto-Plan)
Contexto
Sesión 105 de la Auditoría Suprema: revisión exhaustiva de seguridad y correctness del dominio blueprints / Auto-Plan.
Hallazgos: 61 crudos → 40 únicos → 22 confirmados (7 ALTA, 12 MEDIA, 3 BAJA).
Decisión de Edu: “Arreglar todo de una vez. Definitivo. No se parceliza.”
Decisión 1: Arreglar todos los 22 hallazgos en UNA sola PR
Discusión: Algunas PRs grandes (>50-100 KB de diff) se pueden parcelar en sub-PRs por dominio o por tipo de problema. Ejemplo:
- PR1: Permisos + access control.
- PR2: Cross-tenant + aislamientos.
- PR3: Correctness (N+1, endpoint roto, etc.).
Razón del NO-parcelizar:
- Los hallazgos están entrelazados: Broken Access Control + Auto-Plan costoso → necesario bloquear simultáneamente.
- Prompt global compartido + resemantización de AIPrompt → una sola migración de datos (si parcelizo, tengo dos migraciones conflictivas).
- Tests nuevos requieren todos los fixes simultáneamente para pasar.
- Riesgo de regresión: cada sub-PR deja la aplicación en estado inseguro o incompleto (readonly puede seguir llamando Auto-Plan hasta que la PR de permisos se mergee).
- Versionado: una sola entry en CHANGELOG/RELEASE_NOTES es más limpio y auditable.
Resultado: 1 commit, ~300-400 LOC diff, cambios en 14 archivos. Mergea hoy (2026-06-03), directa a main.
Decisión 2: Dos items quedan para PR distinta (documentado)
Problema: La auditoría identifica otros dos issues del dominio blueprints que sí se parcelizan:
2a. Handler global de str(exc) en config/urls.py
Problema: ~509 endpoints de CreaRack exponen str(e) en los exception handlers (fuga de información interna: stack traces, SQL, rutas, etc.).
Por qué NO se arregla en s105:
- Cambio transversal a todo el proyecto (no solo blueprints).
- Side effects desconocidos: algunos endpoints pueden depender de mensajes específicos en tests o cliente.
- Requiere auditoría de cada handler para decidir error message genérico vs específico.
- Risk level: MEDIA (vs ALTA en permisos).
Acción: se documenta en RELEASE_NOTES como “fuera de tanda”. PR aparte cuando sea el momento.
2b. Auto-Plan asíncrono (Celery / tarea background)
Problema: magic_import_blueprint() es síncrono, llamada IA bloqueante (puede tardar 5-30s). El Map Editor no tiene UI de spinner/polling.
Por qué NO se arregla en s105:
- Cambio de arquitectura (no solo seguridad): necesita Celery + Redis queue.
- Toca frontend (Map Editor JS): requiere UI de spinner + polling / WebSocket.
- Puede requerir cambios en la DB (tabla de Jobs, status tracking).
- Risk level: MEDIA (vs ALTA en decompression bomb).
- Scope: Multi-sprint (s105 = 1 sesión, async = 2-3 sesiones).
Acción: se documenta como “fuera de tanda”. Feature page separada cuando se inicie.
Mención en RELEASE_NOTES: “Conversión de Auto-Plan a asíncrono → PR propio (frontend). Prioridad: media. Tracking: issue #XYZ.”
Decisión 3: Naming del prompt activo
Alternativas consideradas:
__brain_active__(opción elegida)__reserved_active__active(muy ambiguo, conflictaría fácilmente con saved prompts)- Un boolean
is_activeen el modelo (añade lógica, violaría single-name per org)
Razón de __brain_active__:
- Naming magic (double underscore) es convención Python para privado/reservado.
- Descriptivo:
__brain_active__→ “prompt activo del AI Brain”. - Evita colisión: un usuario no llamaría su saved prompt
__brain_active__casualmente. - Validación en API:
if name == "__brain_active__": return 400.
Decisión 4: Fallback a fichero semilla (no error)
Alternativas:
- A. Semilla como fallback (opción elegida): si una org no tiene prompt activo, lee
prompts/blueprint_analyst.md(read-only). - B. Error 500: si no existe prompt activo, falla en Auto-Plan.
- C. Prompt vacío: si no existe, usa string vacío (riesgo: IA recibe instrucción nula).
Razón de A (fallback):
- Backward compatibility: orgs nuevas o viejas sin personalización siguen funciona.
- UX: no confunde al usuario con “prompt no disponible”.
- Migración suave: puedo hacer rolling migration sin downtime.
- Fichero read-only garantiza que nunca se corrompe (nadie lo toca).
Decisión 5: Soft-delete de racks en blueprints
Alternativas:
- A. Hard-delete (antes, endpoint
delete_rack_total): borra inmediatamente, irreversible. - B. Soft-delete + papelera (opción elegida): marca
deleted_at, recuperable. - C. “Archive”: move a tabla
ArchivedRack(overhead de tamaño).
Razón de B (soft-delete):
- Consistencia: el resto del producto (racks, blueprints) usa soft-delete.
- Auditoría: queda trace de qué se borró, cuándo, quién.
- Recuperación: usuario borra accidentalmente un rack → papelera lo recupera.
- RLS: filtro
deleted_at__isnull=Trueen todos los listados de edición.
Decisión 6: Limit 25 MB para Auto-Plan
Alternativas:
- 10 MB (muy restrictivo, rechaza imágenes normales)
- 25 MB (opción elegida)
- 100 MB (muy permisivo, DoS)
- Sin límite (riesgo de abuso)
Razón de 25 MB:
- Típica: imagen de plano de datacentro A4 a 300dpi ≈ 8-15 MB.
- Buffer: 25 MB = 1.5x - 2x la típica (edge cases, pantallazos múltiples).
- Decompression: Pillow puede expandir <25 MB a >64 MP (controlado con guard).
- IA: Google/Claude API generalmente limitan a 20-30 MB por request.
Decisión 7: Guard de decompression bomb: 64 MP
Alternativas:
- 16 MP (muy restrictivo)
- 32 MP (intermedio)
- 64 MP (opción elegida)
- 256 MP (muy permisivo, slow)
Razón de 64 MP:
- Típica: plano de 4000x3000 = 12 MP. Mapa grande 8000x6000 = 48 MP.
- Ultra-wide: mosaicos o stitched images hasta 64 MP son realistas.
- Tiempo: procesamiento de 64 MP = segundos, acceptable.
- Seguridad: rechaza intentos de cuelgue tipo “1 pixel × 2^32 píxeles” expandido a gigabytes.
Follow-ups documentados
- PR handler global
str(exc): transversal aconfig/urls.py. Timing: próximas 1-2 sprints. - PR Auto-Plan async: Celery + frontend. Timing: próximas 2-3 sprints. Depende de: definición de formato de job, schema de polling, WebSocket vs HTTP polling.
- Prompt migration script (si aplica): script para migrar orgs existentes sin prompt activo (opcional, fallback ya cubre).
Checklist de verificación (s105)
- 22 fixes arreglados y testeados.
- 10 tests nuevos en
test_blueprints_security.py. -
require_permaplicado a ~25 endpoints. - AIPrompt resemantizado (BD-backed, per-org).
- Auto-Plan: límite 25 MB, atomic, audit log, error genérico.
- Decompression bomb: guard 64 MP.
- Racks soft-deleted no aparecen en mapas.
-
/placementsendpoint reparado. - N+1 en
/listarreglado. - Tests local: 54 verdes, ruff + django check limpios.
- CHANGELOG + RELEASE_NOTES actualizados.
Véase también
- [[feature—blueprints—auditoria-s105]]
- [[entity—core—service—require-perm]]
- [[entity—blueprints—model—aiprompt]]
- [[concept—saas—multi-tenancy]]