Resumen ejecutivo
Severidad: CRÍTICA (aislamiento multi-tenant quebrado)
Status: 🟢 CERRADO (2026-06-11, commit e786894)
Período afectado: Desde refactor a Django (mono-tenant → multi-tenant incompleto)
Usuarios en riesgo: Cualquier principal autenticado sin organización asignada, o endpoints legacy anónimos
Qué pasaba
El código heredado contenía Organization.objects.first() como fallback en ~26 call-sites. Consecuencia: un principal sin org caía automáticamente al primer tenant de la BD.
Escenarios explorados
Lectura cross-tenant (confirmado)
- GET /api/users/ → listaba todos los usuarios del tenant #1
- GET /api/search → buscaba entre los datos del tenant #1 (equipos, racks, etc.)
- GET /api/logs/data → exponía logs de operaciones ajenas
- GET /api/status/ → mostraba counts de racks/devices del tenant #1
- GET /api/settings/company → leía nombre, dirección, logo de la empresa ajena
- Vista dashboard de racks (
/) → mostraba todos los racks del tenant #1 - Vista reporte de proyecto (
/report) → expone datos del tenant #1
Escritura cross-tenant (confirmado)
- PUT /api/settings/company → podía editar nombre/dirección/logo del tenant #1
- PUT /api/settings/session-timeout → podía cambiar timeout de sesión del tenant #1
- PUT /api/settings/onboarding/complete → podía marcar onboarding completado en tenant #1
- POST /api/users/ → podía crear usuarios en el tenant #1 (como admin sin org)
- PUT /api/users/{id} → podía editar usuarios del tenant #1
- DELETE /api/users/{id} → podía eliminar usuarios del tenant #1
- POST /api/admin/impersonate → podía hacerse pasar por usuarios del tenant #1
- PUT /api/admin/permissions → podía cambiar permisos de usuarios del tenant #1
- Vista rack editor (
/rack/{id}) → cargaba el rack sin scope de org (404cross-tenant)
Raíz causa
- Arquitectura heredada: código mono-empresa que usaba
Organization.objects.first()como “la” org por defecto - Refactor incompleto: al pasar a multi-tenant, no se eliminaron los fallbacks; solo se agregó
request.user.organization - Falta de validación: endpoints read-only retornaban datos, endpoints write permitían cambios, sin validar org context
Cómo se encontró
Auditoría Suprema (2026-06): reporte de seguridad R6 flagged “Aislamiento multi-tenant incompleto”. Análisis manual de core/api/*, core/views, racks/views → encontrados 26 call-sites.
Solución
Tanda T5 (2026-06-11): eliminación del fallback + fail-closed design:
- ✅
get_current_org(request)retorna None si no hay org (no intenta adivinar) - ✅
require_org(request)nuevo helper: lanza 403 si no hay org - ✅ Endpoints read-only: retornan listas vacías, defaults, o counts = 0
- ✅ Endpoints write: lanzan 404 si
get_current_org()es None - ✅ Vistas (views.py): dashboard y reports muestran vacío
- ✅ Bonus:
rack_editorahora scoped a org (antesget_object_or_404(Rack, id=...)sin org)
Cambios de código
Eliminación de fallback (~26 sites):
# ✗ ANTES (inseguro)
org = request.user.organization or Organization.objects.first()
# ✓ DESPUÉS (fail-closed)
org = get_current_org(request) # retorna None si no hay org
if not org:
return [] # read-only: vacío
# o raise HttpError(404, ...) para write
Promoción de utility:
require_orgmovido demonitoring/api/commonacore.utils- Re-exportado en
monitoring/api/common(backward compat)
Scope de rack_editor:
# ✗ ANTES
rack = get_object_or_404(Rack, id=rack_id) # sin org
# ✓ DESPUÉS
org = request.user.organization if hasattr(...) else None
rack = get_object_or_404(Rack, id=rack_id, organization=org) # 404 cross-tenant
Testing
14 tests nuevos (tests/api/test_t5_org_fallback.py):
- ✓ Admin sin org no ve usuarios del tenant #1
- ✓ CREATE/UPDATE/DELETE de usuarios 400-404 sin org
- ✓ Settings (company, timeout, onboarding) 404 sin org
- ✓ Search, logs, status retornan vacío sin org
- ✓ Dashboard vacío sin org
- ✓
rack_editor404 cross-tenant - ✓ Helper
require_orgraises/returns ok
Impacto
Breaking change: Cualquier cliente/bot que dependiera de Organization.objects.first() fallará (intencional).
No-impact: Todo principal normalmente tiene org asignada. Solo afecta a:
- Testing con users sin org
- Legacy endpoints que permitían anónimo
- Bots internos que asumían fallback
Status actual (2026-06-11)
- ✅ Merged a main (e786894)
- ✅ Suite en Docker sin regresiones (diff de fallos = 0)
- ✅ RELEASE_NOTES actualizado (s128)
- ✅ CHANGELOG actualizado (Unreleased 2026-06-11)
Véase también
- [[decision—20260611—t5-organizacion-fallback-multitenant]]
- [[entity—core—utility—require-org]]
- [[concept—saas—multi-tenancy]]
- [[entity—core—model—organization]]
- [[entity—core—model—user]]