OWASP ZAP en CI sin romper todas las compilaciones
El escaneo dinámico en un pipeline falla por motivos predecibles: sin autenticación, sin datos sembrados, un escaneo de cuarenta minutos y un umbral que rompe la build por alertas informativas. Cómo configurar ZAP para que el resultado se crea.
Meter un escáner dinámico de seguridad en el pipeline suena obviamente bien y suele acabar de una de dos formas. O rompe compilaciones por los flags de una cookie en un recurso estático hasta que alguien lo pone en "no fallar nunca", o corre sin autenticar contra una página de login e informa de nada durante un año mientras todo el mundo asume que está cubierto.
OWASP ZAP funciona bien en CI, pero solo con cuatro cosas configuradas a conciencia.
Escanea un entorno desplegado, no un contenedor recién arrancado
ZAP necesita una aplicación con datos dentro. Un contenedor recién levantado con la base de datos vacía no tiene objetos que enumerar, ni formularios con estado realista, ni zona autenticada que explorar. El escaneo encuentra la página de login y el endpoint de salud.
Ejecútalo contra el entorno de vista previa efímero o contra el despliegue de staging, después del paso de datos de siembra. Si tu pipeline no crea uno, eso es lo primero que hay que construir; todo lo de abajo depende de ello.
Autentícate, y demuestra que seguiste autenticado
El marco de automatización resuelve esto en un plan YAML, no con las viejas opciones de línea de comandos:
env:
contexts:
- name: app
urls: [ "https://staging.ejemplo.com/" ]
includePaths: [ "https://staging.ejemplo.com/.*" ]
excludePaths: [ ".*/logout.*", ".*/admin/peligro.*" ]
authentication:
method: browser
parameters:
loginPageUrl: "https://staging.ejemplo.com/login"
sessionManagement: { method: headers }
users:
- name: tester
credentials: { username: "${ZAP_USER}", password: "${ZAP_PASS}" }
jobs:
- type: spiderAjax
parameters: { context: app, user: tester, maxDuration: 5 }
- type: activeScan
parameters: { context: app, user: tester, maxRuleDurationInMins: 3 }
- type: report
parameters: { template: sarif-json, reportFile: zap.sarif }
El excludePaths para el cierre de sesión no es opcional. Tampoco lo es una expresión de verificación que le diga a ZAP qué aspecto tiene una página autenticada, para que vuelva a autenticarse en vez de escanear en silencio como usuario anónimo. Mira en las estadísticas de la ejecución el número de peticiones autenticadas; si ronda cero, el escaneo fue decorativo.
Rastrea una aplicación de página única con la araña AJAX
La araña tradicional sigue enlaces del HTML devuelto. Una aplicación React o Vue devuelve un div vacío y un bundle, así que la araña tradicional encuentra una URL. La araña AJAX conduce un navegador real y sigue lo que la aplicación hace de verdad.
Aun así se dejará rutas que exigen un estado concreto para alcanzarse. Importa directamente la definición de la API —ZAP lee OpenAPI, GraphQL y SOAP— para que el escáner vea todos los endpoints y no solo los que un rastreador encontró por casualidad. En un producto orientado a API, esa importación importa mucho más que la araña.
Falla por lo que merece fallar
Los umbrales de alerta por defecto te van a romper la build porque a un PNG le falta X-Content-Type-Options. Haz esto en su lugar:
- Mantén un fichero de filtros de alerta que degrade a informativas las alertas ya aceptadas, con un comentario que explique por qué se acepta cada una y quién lo decidió.
- Rompe la build solo con alertas de confianza alta y riesgo alto o medio.
- Escribe todo lo demás en el informe y publícalo como artefacto de la compilación.
Si tu plataforma ingiere SARIF —el análisis de código de GitHub lo hace— genera SARIF y deja que esa interfaz gestione el triaje, la deduplicación y la lógica de "nuevo desde la última ejecución". Es mucho mejor que una puerta de aprobado/suspenso, porque pone el hallazgo junto al código sin bloquear una entrega por un falso positivo.
Mantén el escaneo dentro del presupuesto de tiempo del pipeline
Un escaneo activo completo de una aplicación grande tarda horas. Nadie espera horas por una pull request. Divídelo:
- En cada pull request: el escaneo base (solo pasivo, sin ataques) más la importación de la API. Dos o tres minutos, caza regresiones de cabeceras y cookies y cualquier endpoint nuevo sin autenticar.
- Cada noche contra staging: el escaneo activo completo autenticado, con los resultados yendo al backlog de seguridad y no al estado de la compilación.
Esa separación es la diferencia entre un escáner que los desarrolladores toleran y uno que desactivan.
Lo que encuentra y lo que no
ZAP es fiable en las clases de inyección, en cabeceras de seguridad ausentes, en atributos de cookies, en contenido mixto, en listados de directorio y en algunos casos de inyección SQL y de comandos donde la respuesta difiere de forma medible. Son hallazgos reales y son baratos de cazar de forma continua.
No encuentra fallos de autorización, que es el defecto con más probabilidad de hacerte daño de verdad, porque no tiene un modelo de qué objetos pertenecen a qué usuario. No encuentra fallos de lógica de negocio. No entiende que el endpoint que acaba de llamar diez mil veces ha enviado diez mil correos.
Así que trata a ZAP como tratas a un linter: caza barato y constantemente las regresiones que un humano ya conoce, y libera a ese humano para buscar lo que un escáner no puede ver. Los dos tienen sitio en el programa; ninguno sustituye al otro.