CreaRack-SL

Auditoría Suprema 2 · Tanda 1 (core) — cerrar gates de permiso que faltaban

Visión general

Primera tanda de arreglos de la Auditoría Suprema 2 (segunda ronda de revisión de seguridad, 31-08-2026 — la primera ronda fue [[feature—security—s104-auditoria-suprema-fixes-core]]). Cierra 4 hallazgos ALTA de control de acceso, todos en el dominio core y todos de la misma clase: un endpoint o vista que mutaba/leía datos comprobando solo la pertenencia a la organización, nunca el NIVEL de permiso del usuario.

Versión: v1.90.0 · PR: #476 · Fecha: 2026-08-31 · Autor: Edu (Claude Fable 5) · Ámbito: seguridad · control de acceso (core)

Los 4 hallazgos ALTA

1. Escalada de privilegios al crear/promover usuarios

  • Función afectada: core.api.users::create_user y update_user (POST /api/users/, PUT /api/users/{id})
  • Riesgo: Quien tenía users:admin podía crear un usuario con role="admin" (que otorga admin en TODOS los scopes: terminal, network, racks…) o promover a otro a ese role, ganando permisos que él mismo no tenía.
  • Fix: Nueva _role_exceeds_grantor() compara lo que el role otorga, scope a scope, contra los permisos efectivos del solicitante (get_effective_permissions), y devuelve 403 si excede — el mismo control que core/api/admin.py ya aplicaba a ModulePermission/TemporaryAccess. Se valida además que el role sea uno de los válidos (VALID_ROLES).
  • Líneas: core/api/users.py (+27 LOC función _role_exceeds_grantor y sus dos call sites).
  • Test: tests/api/test_users_authz.py::TestUserPrivilegeEscalation (4 tests: crear admin rechazado, crear readonly aceptado, promover a admin rechazado, admin pleno sí puede).

2. Un admin de organización tomaba el control de un superuser de su org

  • Funciones afectadas: core.api.users::update_user, delete_user, delete_user_mfa_device
  • Riesgo: Los tres handlers solo comprobaban la pertenencia a la organización, nunca si el objetivo era is_superuser/is_staff — un admin normal podía cambiarle la contraseña, borrarlo o quitarle el MFA a un superuser de su propia org.
  • Fix: Los tres rechazan con 403 si el objetivo es superuser/staff y el solicitante no lo es. Los tres pasan además de get_current_org a require_org (fail-closed).
  • Líneas: core/api/users.py (+3 guards de ~4 LOC cada uno).
  • Test: tests/api/test_users_authz.py::TestSuperuserProtection (3 tests: password rechazado, delete rechazado, superuser gestionando a otro superuser sí funciona).

3. 13 mutadores HTMX de racks/papelera/grupos sin ningún gate de permiso

  • Módulo afectado: core/htmx_views.py (racks_create, rack_detail, rack_delete, rack_clone, trash_restore, trash_delete_permanent, trash_empty, groups_create, group_delete, group_rename, groups_assign, device_groups_create, device_group_delete)
  • Riesgo: Estas vistas solo llevaban @login_required. Un usuario readonly podía crear/borrar/clonar racks y vaciar la papelera entera desde la interfaz — mientras sus gemelas de la API Ninja ya exigían racks:edit/racks:admin.
  • Fix: Gate has_permission(request.user, "racks", level) + nuevo helper _deny() que devuelve un fragmento HTML 403 (en vistas Django clásicas no se usa require_perm, que lanza el HttpError de Ninja → 500 sin gestionar). racks:edit en creación/edición/clonado/grupos, racks:admin en el borrado permanente y el vaciado de papelera.
  • Modularización: Device Groups (Terminal) se extrajo a core/htmx_device_groups.py — core/htmx_views.py rondaba el techo de 500 LOC lógicas de la Regla 5. Rutas y comportamiento sin cambios, actualizado el import en core/htmx_urls.py.
  • Test: tests/test_htmx_authz.py (9 tests: racks, papelera y grupos, con readonly/operator/admin).

4. Contrato T5 desalineado tras migrar a require_org

  • No es un hallazgo de seguridad nuevo, sino un ajuste de contrato derivado del fix nº1: al pasar create_user/update_user a require_org (fail-closed), un admin sin organización ahora recibe 403 en vez del 400/404 anterior. El invariante T5 (no tocar el primer tenant) se mantiene — solo cambia el código de estado. Actualizadas las aserciones de tests/api/test_t5_org_fallback.py.

Tests añadidos

  • tests/api/test_users_authz.py — 7 tests nuevos (escalada de role + protección de superuser).
  • tests/test_htmx_authz.py — 9 tests nuevos (racks/papelera/grupos por HTMX).
  • tests/api/test_t5_org_fallback.py — 2 aserciones ajustadas al nuevo contrato 403.

Total: 16 tests nuevos, todos verdes.

Patrón aplicado

Mismo patrón que [[decision—20260609—require-perm-unified-authorization]] fijó para network: require_perm/has_permission + require_org como gate uniforme, aquí extendido a las vistas HTMX clásicas de core (que no pueden usar require_perm directamente porque lanza HttpError de Ninja). Ver [[entity—core—service—has-permission]] para el evaluador de permisos subyacente.

Documentación

  • CHANGELOG.md — entrada [1.90.0] con detalle de los 4 fixes.
  • RELEASE_NOTES.md — entrada de usuario explicando el impacto sin jerga técnica.

Véase también

  • [[entity—core—service—has-permission]]
  • [[decision—20260609—require-perm-unified-authorization]]
  • [[feature—security—s104-auditoria-suprema-fixes-core]]
  • [[concept—saas—multi-tenancy]]
  • [[entity—racks—model—rack]]
  • [[decision—20260602—auditoria-suprema-4-hallazgos-alta]]
  • [[entity—core—model—user]]