Por Qué Semgrep, No Regex
El problema fundamental con el scanning de seguridad basado en grep es que el código es un árbol, no un string. Al considerar la detección de SQL injection en Python:
# Grep atrapa esto:
cursor.execute("SELECT * FROM users WHERE id = " + user_id)
# Pero se pierde esto (misma vulnerabilidad, distinto formato):
query = "SELECT * FROM users WHERE id = " + user_id
cursor.execute(
query
)
# Y da falso positivo en esto (seguro, parametrizado):
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
Semgrep trabaja sobre el AST (Abstract Syntax Tree), así que entiende la estructura del código, no solo el texto:
rules:
- id: python-sql-injection
patterns:
- pattern: cursor.execute($QUERY, ...)
- pattern-not: cursor.execute("...", (...))
- metavariable-pattern:
metavariable: $QUERY
patterns:
- pattern: $X + ...
- pattern: f"..."
- pattern: "...".format(...)
- pattern: "..." % ...
message: >
Query SQL construida con concatenación de strings.
Utilizar queries parametrizadas.
severity: ERROR
languages: [python]
Taint Analysis: Siguiendo los Datos
Acá es donde Semgrep va más allá del pattern matching. El modo taint rastrea el flujo de datos de sources a sinks:
rules:
- id: express-xss
mode: taint
message: >
Input del usuario fluye a la respuesta HTML sin sanitización.
Esto es una vulnerabilidad de Cross-Site Scripting (XSS).
severity: ERROR
languages: [javascript, typescript]
pattern-sources:
- patterns:
- pattern: req.query.$PARAM
- patterns:
- pattern: req.body.$PARAM
- patterns:
- pattern: req.params.$PARAM
pattern-sinks:
- patterns:
- pattern: res.send($PAYLOAD)
- patterns:
- pattern: res.write($PAYLOAD)
pattern-sanitizers:
- patterns:
- pattern: DOMPurify.sanitize(...)
- patterns:
- pattern: encodeURIComponent(...)
Esto atrapa:
// DETECTADO: datos tainted fluyen de source a sink
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Resultados para: ${query}</h1>`); // XSS!
});
// NO DETECTADO: sanitizer rompe la cadena de taint
app.get('/safe', (req, res) => {
const query = DOMPurify.sanitize(req.query.q);
res.send(`<h1>Resultados para: ${query}</h1>`); // Seguro
});
Construyendo un Set de Reglas Específico de la Org
Más allá de las reglas de seguridad, el set de reglas puede expandirse a patrones específicos de la organización:
rules:
- id: usar-http-client-interno
patterns:
- pattern-either:
- pattern: fetch(...)
- pattern: axios.get(...)
- pattern: axios.post(...)
paths:
include: ["src/**"]
exclude: ["src/lib/http-client/**", "**/*.test.*"]
message: |
Utilizar el HTTP client interno (`@myorg/http-client`) en lugar de
fetch/axios directo. Gestiona autenticación, reintentos, circuit breaking
y tracing distribuido automáticamente.
severity: WARNING
languages: [javascript, typescript]
- id: sin-acceso-directo-a-db
patterns:
- pattern-either:
- pattern: prisma.$MODEL.$METHOD(...)
- pattern: db.query(...)
- pattern-not-inside: |
class $REPO { ... }
paths:
include: ["src/**"]
exclude: ["src/repositories/**"]
message: |
Acceso directo a la base de datos fuera de la capa de repositorios.
Todas las operaciones de DB deberían ir por clases repository.
severity: WARNING
languages: [javascript, typescript]
Resultados Después de Seis Meses
Los números hablan solos:
Antes de Semgrep:
- Tiempo de review manual de seguridad: ~45 min/PR
- Turnaround de PR: 2,3 días promedio
- Findings de seguridad por trimestre: ~12 (encontrados en review)
- Findings perdidos (encontrados en prod): ~4/trimestre
Después de Semgrep (+ review manual reducido):
- Tiempo de scan automatizado: ~90 segundos
- Review manual (solo PRs complejos): ~15 min
- Turnaround de PR: 4 horas promedio
- Findings de seguridad por trimestre: ~35 (atrapados en CI)
- Findings perdidos (encontrados en prod): 0 el último trimestre
Reglas custom escritas: 47
- Patrones de seguridad: 23
- Enforcement de uso de API: 12
- Guardrails de arquitectura: 8
- Calidad de código: 4
La mayor victoria no es encontrar más bugs, es liberar al equipo de seguridad para hacer threat modeling real y revisión de arquitectura en vez de chequear si se usaron queries parametrizadas. Lo mecánico es lo que las máquinas hacen bien.