Task #279 · gates de permisos que faltaban en 3 zonas + test de contrato universal
Cuándo
06-09-2026, v1.118.0 (commit b160abd9, PR #511). Task #279 se había descrito el 30-08-2026, antes de que la cola de auditoría #286 (31-08 a 05-09) cerrara ya 2 de las 4 zonas originales (racks/api/library*.py y tutor.py).
Síntomas visibles
Ninguno reportado por un usuario — el hallazgo salió de una tarea de auditoría interna. Las 4 zonas descritas en la task ya no coincidían con el código real: 2 habían quedado cerradas de rebote por la cola #286, y las 2 restantes seguían con el hueco original.
Causa raíz
El mismo patrón sistémico de siempre (ver [[incident—20260831—auditoria-suprema-2-tanda1-monitoring-racks]]): require_perm/has_permission no es un middleware automático, así que un handler nuevo o copiado puede quedarse sin gate o con el dominio equivocado, y nada lo detecta hasta que alguien lo audita a mano.
Dos zonas quedaban con el gate real pero mal dirigido o insuficiente:
core/htmx_device_groups.py: los mutadores de crear/borrar un grupo de dispositivos exigíanhas_permission(request.user, "racks", ...)— dominio equivocado, copiado de los mutadores de racks. Un grupo de dispositivos es dominio network, no racks.network/api/scripts.py::get_scripts: único endpoint del router sin ningún gate — devolvía los 6 scripts de red con suscript_contenta cualquier usuario autenticado, incluido un rol de solo lectura.monitoring/api/insight_execution.py::apply/rollback: gateados concns:edit, el mismo nivel que el resto de mutadores de CNS — pero estos dos despachan comandos SSH reales a equipos de la red, un nivel de riesgo distinto al de editar un insight.
Fix aplicado
Commit b160abd9512ee39266a0fa1f733b1fc3007da7b8 (PR #511):
core/htmx_device_groups.py:network:edital crear,network:adminal borrar (igual que sus gemelos de la API Ninjanetwork/api/device_groups.py), máslog_actionen ambos mutadores (no lo tenían). Detalle en [[entity—core—service—has-permission]].network/api/scripts.py::get_scripts:require_perm(request, "network", "view").monitoring/api/insight_execution.py::apply/rollback: subidos decns:editacns:admin(decisión de Edu).dry_runse queda eneditporque no ejecuta nada.- Preventivo nuevo:
tests/api/test_write_endpoints_permission_contract.py, un test que recorre TODOS los routers Ninja registrados y exige un gate de permiso en toda operación POST/PUT/PATCH/DELETE, con lista blanca razonada para las exenciones reales. Detalle completo en [[entity—core—service—require-perm]].
El test se validó con un simulacro del camino de fallo: se quitó a mano el require_perm de un endpoint ya gateado (delete_stencil) y el test se puso en rojo nombrándolo, antes de confiar en que vigila algo de verdad.
Lecciones
- El mismo hallazgo (gate ausente o con scope equivocado) ya había aparecido 4 veces en 4 dominios distintos antes de este commit, contando la cola #286. Un test puntual por zona no lo iba a prevenir la próxima vez — hacía falta un test que recorriera la superficie entera de una vez.
- Una sonda ingenua que solo busca
require_perm(como texto genera muchos falsos positivos: los handlersasyncgatean consync_to_async(require_perm)(sin paréntesis de llamada tras el nombre) y varios endpoints legacy delegan en una función hermana que sí gatea. Hubo que enseñarle esos dos patrones al detector en vez de esconderlos en la lista blanca.
Preventivos futuros
- Deuda declarada, sin cerrar en este commit:
monitoring/api/insight_execution.py::explain_insightno tiene gate, mientras su endpoint hermanotutor_askexigecns:view. No se tocó porqueexplain_insighttambién acepta el JWT del Agente instalado, y requiere decidir cómo convive esa autenticación conrequire_perm. - El nuevo test de contrato solo cubre escrituras (POST/PUT/PATCH/DELETE). Una lectura GET que filtre datos sensibles — como el propio
get_scriptsde esta misma tanda — no la ve, y necesitaría un test de contrato equivalente para lecturas.
Véase también
- [[entity—core—service—has-permission]]
- [[entity—core—service—require-perm]]
- [[decision—20260823—racks-settings-broken-access-control]]
- [[incident—20260831—auditoria-suprema-2-tanda1-monitoring-racks]]
- [[incident—20260904—cola-auditoria-b-cns-insights]]
- [[concept—saas—multi-tenancy]]