SAST, análisis estático de código
SAST (Static Application Security Testing) es analizar el código fuente buscando patrones peligrosos sin llegar a ejecutarlo. Es el equivalente a un corrector ortográfico, pero para fallos de seguridad.
Su gran ventaja es que funciona desde el primer momento: no necesitas desplegar nada, ni una base de datos, ni un entorno. Con el código basta, así que puede correr en cada push y darte respuesta en segundos.
Cómo funciona por dentro
Un analizador estático no lee el código como texto. Lo convierte en un árbol sintáctico y busca formas, no cadenas.
Por eso una regla que busque eval($X) encuentra las tres:
eval(entrada);
eval( entrada );
eval(
entrada
);
Los más avanzados hacen además taint tracking: siguen el rastro de un dato desde donde entra (una petición HTTP) hasta donde se usa (una consulta SQL). Si el camino no pasa por ninguna validación, saltan.
Laboratorio: analizar el código
Usamos Semgrep, que es rápido, no necesita compilar y trae miles de reglas de serie:
cd devsecops-demo
docker run --rm -v "$PWD:/src" semgrep/semgrep:latest \
semgrep --config=auto /src
Tarda unos dieciséis segundos y no requiere cuenta. Los 9 hallazgos reales:
Dockerfile:22 [ERROR] missing-user
infra/main.tf:26 [WARNING] s3-public-read-bucket
infra/main.tf:52 [WARNING] aws-db-instance-no-logging
infra/main.tf:58 [WARNING] rds-insecure-password-storage-in-source-code
infra/main.tf:59 [WARNING] rds-public-access
src/config.js:18 [ERROR] detected-aws-access-key-id-value
src/index.js:5 [INFO] express-check-csurf-middleware-usage
src/routes.js:34 [WARNING] eval-detected
src/routes.js:34 [ERROR] code-string-concat
Dos observaciones antes de seguir.
Primera: Semgrep encuentra la clave de AWS que gitleaks no vio. En el capítulo anterior gitleaks detectó dos secretos, pero el identificador AKIA... de la línea 18 se le escapó. Aquí sí aparece. No es que una herramienta sea mejor: es que miran de forma distinta, y por eso se usan las dos.
Segunda: analiza también el Dockerfile y el Terraform. Semgrep no es solo para código de aplicación. Ya te está avisando de que el contenedor corre como root y de que el bucket es público — cosas que veremos a fondo en los capítulos 5 y 7.
El hallazgo estrella
src/routes.js:34 eval-detected
34┆ const resultado = eval(req.query.expr);
eval() ejecuta como código lo que le pases. Y aquí le estamos pasando, literalmente, un parámetro de la URL. Levanta la aplicación y compruébalo:
npm install && npm start
curl "http://localhost:3000/calcular?expr=7*6"
{"resultado":42}
Ha ejecutado 7*6 en tu servidor. Cambia esa expresión por algo menos amable y tienes ejecución remota de código. Esto no es un aviso teórico.
Lo que las reglas gratuitas no ven
Aquí viene la parte interesante, y es una lección más valiosa que cualquier hallazgo.
En src/routes.js hay una inyección SQL de manual:
const { rows } = await pool.query(
'SELECT * FROM usuarios WHERE id = ' + req.params.id
);
Semgrep no la detecta. Ni con --config=auto, ni con p/javascript, ni con p/security-audit. El motivo es que el taint tracking para librerías de base de datos forma parte de las reglas de pago.
Es un recordatorio útil: pasar el escáner sin hallazgos no significa que el código sea seguro. Significa que no ha saltado ninguna de las reglas que tienes activadas.
La buena noticia es que escribir la regla cuesta doce líneas.
Escribir tu propia regla
# .semgrep.yml
rules:
- id: sql-por-concatenacion
message: >-
Consulta SQL construida concatenando texto. Si alguno de los trozos viene
del usuario, esto es una inyección SQL. Usa consultas parametrizadas.
languages: [javascript, typescript]
severity: ERROR
patterns:
- pattern-either:
- pattern: $CLIENT.query("..." + $X, ...)
- pattern: $CLIENT.query('...' + $X, ...)
- pattern: $CLIENT.query(`...${$X}...`, ...)
La sintaxis es el propio lenguaje con comodines: $CLIENT y $X capturan cualquier cosa, "..." es cualquier cadena y ... cualquier lista de argumentos. No hay que aprender un lenguaje nuevo.
docker run --rm -v "$PWD:/src" semgrep/semgrep:latest \
semgrep --config=/src/.semgrep.yml /src
❯❯❱ sql-por-concatenacion
22┆ const { rows } = await pool.query(
23┆ 'SELECT * FROM usuarios WHERE id = ' + req.params.id
Cazada. Y ahora cualquiera que escriba esa forma en el repositorio la tendrá marcada automáticamente.
La forma correcta, para comparar:
const { rows } = await pool.query(
'SELECT * FROM usuarios WHERE id = $1',
[req.params.id]
);
El parámetro viaja aparte de la consulta, así que el motor nunca lo interpreta como SQL.
Los falsos positivos son el problema real
Un SAST mal calibrado produce cientos de avisos, el equipo deja de mirarlos y en la práctica te has quedado sin control. Tres cosas que funcionan:
- Empieza solo por
ERROR. LosWARNINGeINFOcomo informe, sin bloquear. - Silencia con contexto. Un
// nosemgrep: regla-concretaen la línea, indicando la regla exacta y por qué. Nunca el fichero entero. - Analiza solo lo que cambia en los pull requests (
--baseline-commit). Nadie va a arreglar de golpe la deuda de cinco años, pero sí evita añadir más.
SAST en el pipeline
# .github/workflows/sast.yml
name: SAST
on: [push, pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- run: semgrep --config=auto --config=.semgrep.yml --sarif -o semgrep.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
El formato SARIF es el estándar para resultados de análisis. Subirlo hace que los hallazgos aparezcan en la pestaña Security del repositorio y como comentarios en línea dentro del pull request, que es donde la gente los va a leer.
¿Y SonarQube?
Semgrep y SonarQube resuelven cosas parecidas con filosofías distintas:
| Semgrep | SonarQube | |
|---|---|---|
| Enfoque | Seguridad, reglas propias fáciles | Calidad global: bugs, cobertura, deuda |
| Instalación | Un contenedor | Un servidor y una base de datos |
| Velocidad | Segundos | Minutos |
| Histórico | No de serie | Sí, con tendencias y quality gates |
Para empezar en DevSecOps, Semgrep. SonarQube gana cuando quieres un panel de calidad para toda la organización, más allá de la seguridad.
Resumen
- SAST analiza el código sin ejecutarlo: rápido, barato y en cada push
- Semgrep encuentra
eval(), secretos y hasta problemas del Dockerfile y del Terraform - Ninguna herramienta lo ve todo: la inyección SQL se le escapa a las reglas gratuitas
- Escribir una regla propia cuesta doce líneas y es la diferencia entre detectar y no detectar
- Los falsos positivos matan el control: empieza por lo crítico y analiza solo lo que cambia
- Sube los resultados en SARIF para que aparezcan donde el equipo los lee
Recursos adicionales
- Documentación de Semgrep
- Semgrep Playground — probar reglas en el navegador
- OWASP Top 10
💡 Recuerda: un escaneo sin hallazgos no dice que tu código sea seguro. Dice que no has activado la regla que lo habría encontrado.
En el próximo capítulo dejamos el código y pasamos al artefacto: la imagen de contenedor.