<!-- Canonical: https://lucioduran.com/es/blog/ai-code-review-ast-analysis-semgrep-custom-rules
Language: es
Content-SHA256: 0b20e3750d2e71558c1087636ad663d000cf0e6edbcb020387d0cebb27812ef5
-->

Developer Tools

# Revisión de Código Automatizada con Semgrep: Reglas Personalizadas, Pattern Matching AST y Scanners de Seguridad Organizacionales

2026-01-154 min lectura

Lucio Durán
Engineering Manager & AI Solutions Architect

También disponible en: [English](<https://lucioduran.com/blog/ai-code-review-ast-analysis-semgrep-custom-rules>)

## 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:

```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:

```yaml
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:

```yaml
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:

```javascript
// 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:

```yaml
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.

## Preguntas frecuentes

### ¿Qué es Semgrep y cómo se diferencia de ESLint u otros linters?

Semgrep es una herramienta de análisis estático que matchea patrones de código usando análisis de Abstract Syntax Tree (AST) en vez de regex basado en texto. A diferencia de ESLint que es específico de JavaScript, Semgrep soporta 30+ lenguajes con la misma sintaxis de reglas. También soporta taint analysis entre funciones para detección de vulnerabilidades de seguridad, cosa que los linters típicamente no pueden hacer.

### ¿Qué es taint analysis y cuándo debería usarlo?

Taint analysis rastrea cómo fluyen los datos desde fuentes no confiables (entrada de usuario, solicitudes HTTP) a través del código hasta sinks peligrosos (queries SQL, comandos shell, salida HTML). Es apropiado utilizarlo para encontrar vulnerabilidades de inyección como SQL injection, XSS o command injection que dependen del flujo de datos más que de patrones individuales de código.

### ¿Puede Semgrep reemplazar el code review humano?

No. Semgrep sobresale en hacer cumplir patrones conocidos de forma consistente, anti-patrones de seguridad, reglas de uso de API, mejores prácticas del framework. Sin embargo, no puede evaluar la corrección de la lógica de negocio, decisiones de arquitectura o legibilidad del código. Su función es automatizar las verificaciones mecánicas para que los revisores humanos puedan concentrarse en los aspectos complejos.

## Tips

1. **Tip:** Se recomienda comenzar con ` pattern-not ` de Semgrep antes de escribir reglas complejas. La mayoría de las reglas de seguridad consisten en 'encontrar X a menos que Y también esté presente.' Por ejemplo, ` fetch() ` sin ` csrf_token ` detecta el 90% de los problemas CSRF con una regla de dos patterns.
2. **Tip:** El modo taint de Semgrep es donde reside la capacidad más significativa. En lugar de buscar patterns en líneas individuales, se definen ` sources ` (entrada del usuario), ` sinks ` (funciones peligrosas) y ` sanitizers ` (funciones de escape). Semgrep rastrea el flujo de datos entre ellos a través de límites de funciones, detectando errores de inyección que las herramientas basadas en grep omiten completamente.
3. **Tip:** Es importante ejecutar ` semgrep --test ` contra las reglas personalizadas con casos de prueba positivos y negativos. Los equipos que escriben reglas con coincidencias demasiado amplias inundan los PRs de falsos positivos, destruyendo la confianza en la herramienta en una semana. Las reglas deben verificarse como el código de producción.

- semgrep
- ast
- code-review
- seguridad
- análisis-estático
- taint-analysis
- reglas-custom
- ci-cd

[Volver al Blog](<https://lucioduran.com/es/blog>)
