Volver
9/2/20268 min de lectura

SCA, SAST y DAST: cómo agregué seguridad de verdad a mi pipeline

Durante mucho tiempo, CI/CD para mí era solo build, test y deploy. Ninguna de esas etapas responde si el código es seguro. Implementé SCA, SAST y DAST en un pipeline real y aprendí en la práctica qué encuentra cada uno — y qué no reemplaza ninguno.

Durante mucho tiempo, cuando pensaba en CI/CD, pensaba básicamente en esto:

Push
Build
Tests
Deploy

Si compilaba y los tests pasaban, listo.

Pero existe un problema: ninguna de estas etapas responde necesariamente si el código es seguro.

Una aplicación puede compilar, pasar todos los tests y seguir siendo vulnerable a SQL Injection, usar una dependencia con un CVE conocido o incluso tener un secreto commiteado en el repositorio.

Fue estudiando seguridad de aplicaciones que empecé a implementar un pipeline de DevSecOps en un proyecto vulnerable que creé justamente para eso.

Y tres conceptos aparecieron todo el tiempo:

SCA, SAST y DAST.

Parecen hacer lo mismo, pero analizan partes completamente distintas de la aplicación.

Primero: ¿cuál es la diferencia entre SCA, SAST y DAST?

La forma más simple que encontré de pensarlos es:

SCA analiza lo que instalaste. SAST analiza lo que escribiste. DAST analiza cómo se comporta tu aplicación cuando alguien intenta romperla.

En un pipeline, podemos imaginarlo así:

Aplicación
SCA
Dependencias
SAST
Código
DAST
App en ejecución

No son competidores.

Uno encuentra problemas que el otro muchas veces ni siquiera puede ver.

1. SCA — Software Composition Analysis

Hoy prácticamente ninguna aplicación se escribe desde cero.

En mi backend Node.js, por ejemplo, tengo dependencias como Express, librerías para JWT, acceso a PostgreSQL y varias otras.

El problema es que:

Mi código
express
jsonwebtoken
drizzle
decenas/cientos de dependencias transitivas

Una vulnerabilidad no tiene que estar en el código que escribí yo.

Puede estar en una librería que instalé.

Ahí es exactamente donde entra el SCA.

En el proyecto, empecé con:

bash
npm audit

npm audit compara el árbol de dependencias del proyecto con advisories de vulnerabilidades conocidas.

Así fue como encontré, por ejemplo, una versión vulnerable de jsonwebtoken.

O sea: podría revisar todo mi código y nunca encontrar ese problema.

El problema estaba en la composición del software.

En el pipeline agregué:

yaml
- name: Audit dependencies
  run: npm audit --audit-level=high

Ahora el CI también verifica las dependencias.

Y --audit-level=high empieza a introducir otro concepto importante: security gate.

Puedo decidir que las vulnerabilidades menores se reporten, mientras que las de riesgo High o Critical bloqueen esa ejecución del pipeline.

2. SAST — Static Application Security Testing

Después de las dependencias, viene el código que escribimos nosotros.

Ahí fue cuando agregué SAST.

Su característica principal es que el SAST puede buscar patrones potencialmente vulnerables sin necesidad de atacar una aplicación en ejecución.

En mi caso usé dos herramientas:

  • CodeQL
  • Semgrep

CodeQL es particularmente interesante porque convierte el código en una representación consultable y busca flujos que correspondan a problemas de seguridad.

En el pipeline:

yaml
- uses: github/codeql-action/init@v4
  with:
    languages: javascript-typescript
    queries: security-extended
 
- uses: github/codeql-action/analyze@v4

También agregué Semgrep:

yaml
- name: Install Semgrep
  run: pip install semgrep
 
- name: Run Semgrep
  run: semgrep scan --config=auto

Durante las pruebas, CodeQL logró señalar problemas que pasarían fácilmente desapercibidos en una revisión rápida, como log injection.

La idea acá es distinta a la del SCA:

SCA
package.json
package-lock.json
dependencia vulnerable
SAST
source code
flujo/patrón vulnerable

Pero todavía existe una limitación importante.

Estamos analizando el código.

No estamos necesariamente observando el comportamiento real de la aplicación mientras está en ejecución.

Ahí es donde entra la parte que me pareció más interesante.

3. DAST — Dynamic Application Security Testing

Con el DAST dejé de simplemente analizar archivos.

Levanté la aplicación y dejé que una herramienta intentara atacarla.

Usé OWASP ZAP.

Mi pipeline pasó a hacer aproximadamente esto:

GitHub Actions
levanta PostgreSQL
ejecuta migrations
levanta backend
crea usuario de prueba
hace login
captura JWT
ejecuta OWASP ZAP

El ZAP recibe mi especificación OpenAPI y empieza a testear los endpoints.

Y ahí apareció una diferencia enorme respecto al SAST.

Puede hacer requests como:

http
GET /notes/search?q='
Authorization: Bearer <token>

y observar qué pasa.

En uno de los scans, ese request resultó en:

500 Internal Server Error

El ZAP entonces levantó una alerta de posible:

SQL Injection
Risk:High
CWE-89
Parameter:q

Eso no significa automáticamente que todo hallazgo del scanner sea una vulnerabilidad confirmada.

En este caso, por ejemplo, la confianza reportada era baja. El siguiente paso es analizar la implementación, reproducir el comportamiento y determinar si realmente existe SQL Injection.

Esta parte es fundamental:

El scanner encuentra indicios. El desarrollador igual necesita entender el finding.

Un problema que encontré implementando DAST

Al principio, el scan parecía funcionar.

Solo que casi todas las peticiones estaban devolviendo:

401 Unauthorized

El ZAP estaba "testeando" mi API, pero no lograba llegar justamente a los endpoints que quería testear.

Fue necesario crear un usuario específico para el DAST durante la ejecución del pipeline:

POST /auth/register
POST /auth/login
JWT
Authorization: Bearer <JWT>
OWASP ZAP

Después apareció otro problema.

Rate limiting.

Había agregado rate limits justamente como protección de seguridad, pero un scanner envía una cantidad enorme de requests.

Resultado:

ZAP
request ×4
429 Too Many Requests

El scanner volvía a perder cobertura.

Este fue un aprendizaje importante: no basta con poner una herramienta de seguridad en el CI y asumir que está testeando tu aplicación correctamente.

Un scan verde con autenticación rota puede ser mucho peor que un scan rojo.

Da una falsa sensación de seguridad.

El secret scanning también se sumó al pipeline

Además de las tres principales, agregué también Gitleaks.

El objetivo es distinto:

Commit → Gitleaks
¿API key?
¿Token?
¿Password?
¿Credential?

Un secreto encontrado puede significar que esa credencial ya tiene que considerarse comprometida.

En GitHub Actions quedó algo así:

yaml
- uses: gitleaks/gitleaks-action@v3
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Eso complementa a los otros scanners.

Cómo quedó el pipeline

Después de juntar todo:

Pull RequestGITHUB ACTIONSBuild✓SCAnpm auditSASTCodeQL+SemgrepSecret ScanGitleaksDASTOWASP ZAP

Pero todavía faltaba algo.

¿Qué pasa cuando alguna herramienta encuentra una vulnerabilidad?

Los findings también necesitan llegar a algún lugar

Otra cosa que implementé fue lograr que los resultados de los scanners no se perdieran en el log de GitHub Actions.

Con OWASP ZAP, por ejemplo, se puede permitir que la propia Action registre los resultados en un GitHub Issue:

yaml
allow_issue_writing: true

Así, el finding no existe solo durante esa ejecución del pipeline. Se convierte en algo que se puede seguir y tratar dentro del propio repositorio.

Con CodeQL, la integración es todavía más directa.

Los resultados del análisis se envían al code scanning de GitHub y aparecen en el área Security and quality, donde se pueden ver los alerts, la severidad, el archivo afectado y seguir la corrección.

Otras herramientas de análisis estático también pueden integrar sus resultados a GitHub a través del formato SARIF:

Scanner
SARIF
GitHub Code Scanning
Security and quality

GitHub interpreta ese archivo y transforma los resultados en code scanning alerts dentro del repositorio.

Entonces empezamos a separar dos responsabilidades importantes:

Encontrar vulnerabilidad → Scanner
Reportar
Issue / Security and quality
Bloquear
Pipeline falla

Porque reportar un problema e impedir que llegue a producción son cosas distintas.

Ahí es donde entran los security gates.

Encontrar una vulnerabilidad no es suficiente

Un scanner puede simplemente generar un reporte y dejar que el pipeline continúe.

Para transformar eso en una política de seguridad de verdad, necesitamos definir security gates.

Por ejemplo:

Informationalreportar
Lowreportar
Mediumpolítica del proyecto
Highbloquear
Criticalbloquear

Si una vulnerabilidad viola la política:

Scanner
Finding
Security Policy
exit code != 0
GitHub Action ❌
PR bloqueado

Y hay una distinción importante acá.

Que el pipeline falle no significa automáticamente que nadie pueda hacer merge.

El repositorio también necesita exigir ese status check a través de las reglas de protección de la branch.

Entonces el flujo completo pasa a ser:

Pull RequestSecurity PipelinePASSFAILMerge permitidoMerge bloqueado

Acá es donde la seguridad deja de ser solo un reporte que nadie lee y empieza a formar parte del proceso de desarrollo.

Ninguna de estas herramientas reemplaza a las otras

Esta fue probablemente la mayor conclusión que saqué implementando todo esto.

Imaginá que instalo una librería vulnerable:

SCA puede encontrarlo.

Ahora imaginá que escribo una query SQL concatenando input del usuario:

SAST puede encontrarlo.

Ahora imaginá que determinado payload provoca un comportamiento inesperado solo cuando la base de datos, la autenticación, el middleware y la aplicación funcionan juntos:

DAST puede encontrarlo.

Y imaginá que tengo una falla de autorización donde:

Usuario A
GET /notes/10
La nota pertenece al usuario B

Tal vez ningún scanner lo encuentre de forma confiable.

Ahí es donde entran la revisión humana, tests específicos y el Threat Modeling.

La seguridad no es:

"Instalé el ZAP, así que ahora mi aplicación es segura."

Es mucho más parecido a:

DependenciasSCA
CódigoSAST
SecretsSecret Scanning
App en ejecuciónDAST
ArquitecturaThreat Modeling
revisión humana

Cada capa cubre una parte distinta del problema.

Lo que cambió en mi forma de ver el CI/CD

Antes pensaba:

"El pipeline verifica si mi código funciona."

Ahora prefiero pensar:

"El pipeline verifica algunas de las condiciones necesarias para confiar en que ese código puede seguir adelante."

Que el build pase no significa código correcto.

Que el test pase no significa código seguro.

Que el SAST pase no significa una aplicación segura.

Que el DAST pase tampoco significa una aplicación segura.

Pero cuando combinamos varias de estas técnicas, aumentamos bastante nuestra capacidad de encontrar problemas antes de que lleguen a producción.

Y, para mí, esa fue la parte más interesante de estudiar DevSecOps en la práctica:

la seguridad deja de ser una etapa hecha al final del proyecto y pasa a formar parte del ciclo normal de desarrollo.