Volver a la wiki

Auditoría Suprema 2 · Cola frontend: CSP del portal de cliente, escapes y CSRF same-origin

Cuándo

05-09-2026 · commit 944eb5a1 (PR #508, v1.115.0). Cierra el dominio frontend de la cola MEDIA/BAJA de la Auditoría Suprema 2 (task #286): del catálogo de 23 hallazgos, 7 ya estaban cerrados por versiones anteriores (#1, #6, #8-#11, #19), 12 se arreglan en este commit y 3 quedan diferidos con motivo. Agente Opus en worktree con las decisiones fijadas por hallazgo; el agente no pudo ejecutar nada (pérdida de aislamiento del worktree cuando el padre hace cd, footgun ya documentado) y biome, vitest, pytest y mypy los corrió la sesión principal. Click-test en Edge pendiente tras el deploy (portal de cliente, gráficos del Observatory, wizard Auto-Provision, credenciales, editor).

Síntomas visibles

  1. Portal público de cliente sin CSP (core/middleware/csp.py, templates/signage/client_portal.html): /client/ estaba exento ENTERO de la cabecera de seguridad, y sus dos <script> inline no llevaban nonce — la única ruta anónima de la app quedaba sin la defensa que el resto de páginas ya tenía. (#20, cubre también #22)
  2. seriesName sin escapar en el tooltip y en la tabla del toolbox de los gráficos ECharts compartidos — dato que viene del tenant (hostname/target de un dispositivo). (#2)
  3. discovery_method y vendor_slug sin escapar en la tabla de resultados de Auto-Provision; vendor_slug además se interpolaba crudo dentro de un atributo data-args — un slug con comilla rompía el HTML. (#17)
  4. Nombre de grupo sin escapar en las <option> del filtro de Signage Content Publish, duplicado en dos sitios del fichero. (#21)
  5. El guard CSRF same-origin solo cubría post: put, patch, delete y upload de ApiService.js seguían mandando el token a cualquier destino, y ApiService-global.js (script clásico de 5 plantillas) no lo tenía en absoluto. (#4, #5)
  6. Forma del error inconsistente: según el endpoint, unos call-sites leían .error y otros .message del objeto que lanza ApiService, y uno de los dos podía salir undefined. (#7)
  7. Fechas de los gráficos fijas en es-ES aunque la interfaz estuviera en otro idioma. (#3)
  8. Secretos descifrados sobreviviendo al cierre del modal de CredentialManager.js: el DOM del modal se inyecta una sola vez, así que cerrar sin cambiar de credencial dejaba las contraseñas en los inputs. (#12)
  9. IP del discovery individual sin validar antes de mandarla al Agente local, y sin encodeURIComponent en la URL. (#15)
  10. checkCollision con un 6º argumento fantasma (depth) que dos llamadas seguían pasando y un comentario que prometía un bloqueo por profundidad que el backend (racks/utils/placement.py) ya no aplica. (#13)
  11. Variable de bucle t sombreando la función de traducción t() dentro de un .map() en results.js — nada de ese bloque se traducía. (#18)

Causa raíz

Fix aplicado

Commit 944eb5a1 (PR #508):

Lecciones

Preventivos futuros

Véase también

Subir