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.