Saltar al contenido principal

Semgrep

Herramienta de análisis estático (SAST) que busca patrones en código fuente. A diferencia de herramientas tradicionales, Semgrep usa patrones similares al código real, lo que facilita escribir y entender reglas personalizadas.

Instalación

# macOS
brew install semgrep

# pip
python3 -m pip install semgrep

# Docker
docker run --rm -v "${PWD}:/src" semgrep/semgrep semgrep --config=auto /src

# Verificar
semgrep --version

Escaneo rápido

# Usar reglas recomendadas de la comunidad
semgrep --config=auto .
# Usar un ruleset específico del registry
semgrep --config=p/owasp-top-ten .
# Escanear solo ciertos lenguajes
semgrep --config=auto --lang=python,javascript src/
# Escanear un archivo o directorio específico
semgrep --config=auto src/api/

Rulesets útiles del registry

RulesetDescripción
p/owasp-top-tenOWASP Top 10
p/security-auditAuditoría general de seguridad
p/secretsSecretos hardcodeados (tokens, passwords, API keys)
p/ciReglas optimizadas para CI (bajo ruido)
p/pythonSeguridad + buenas prácticas Python
p/javascriptSeguridad + buenas prácticas JavaScript
p/golangSeguridad + buenas prácticas Go
p/dockerMejores prácticas en Dockerfiles
p/kubernetesConfiguraciones inseguras en manifiestos K8s
# Combinar varios rulesets
semgrep --config=p/owasp-top-ten --config=p/secrets --config=p/docker .

Formatos de salida

# Tabla en terminal (por defecto)
semgrep --config=auto .

# JSON para post-proceso
semgrep --config=auto --json -o report.json .

# SARIF para GitHub Code Scanning
semgrep --config=auto --sarif -o report.sarif .

# Solo mostrar findings de severidad alta
semgrep --config=auto --severity=ERROR .

Flags útiles

  • --severity=ERROR — solo findings críticos
  • --exclude='tests/**' — excluir directorios
  • --include='*.py' — solo ciertos archivos
  • --max-target-bytes=1000000 — limitar tamaño de archivos
  • --no-git-ignore — incluir archivos en .gitignore
  • --verbose — detalle de reglas ejecutadas
  • --debug — troubleshooting de reglas

Reglas personalizadas

La principal ventaja de Semgrep es lo fácil que es escribir reglas propias. Los patrones se parecen al código real, usando ... como comodín.

Estructura básica

rules:
- id: mi-regla
pattern: <patrón>
message: <mensaje para el desarrollador>
languages: [python]
severity: WARNING # INFO | WARNING | ERROR
metadata:
category: security
cwe: "CWE-89: SQL Injection"

Patrones comunes

Detectar inyección SQL

rules:
- id: python-sqli-format
patterns:
- pattern: cursor.execute(f"...")
message: >
Posible SQL injection — usa consultas parametrizadas
con cursor.execute("SELECT ... WHERE id = %s", (id,))
languages: [python]
severity: ERROR
metadata:
cwe: "CWE-89"

Detectar uso de eval

rules:
- id: no-eval
pattern: eval(...)
message: Evita eval() — usa alternativas seguras como ast.literal_eval()
languages: [python]
severity: ERROR

Detectar secretos hardcodeados

rules:
- id: hardcoded-password
patterns:
- pattern: $VAR = "..."
- metavariable-regex:
metavariable: $VAR
regex: (?i)(password|secret|token|api_key)
message: Posible secreto hardcodeado en $VAR — usa variables de entorno
languages: [python, javascript, typescript]
severity: ERROR

Detectar HTTP sin TLS

rules:
- id: no-http-requests
pattern: requests.get("http://...")
message: Usa HTTPS en vez de HTTP
languages: [python]
severity: WARNING

Operadores de patrones

OperadorUso
patternCoincidencia directa
patternsAND — todos deben coincidir
pattern-eitherOR — cualquiera coincide
pattern-notExcluir coincidencias
pattern-insideBuscar dentro de otro patrón
pattern-not-insideExcluir si está dentro de un patrón
metavariable-regexFiltrar metavariables con regex
metavariable-comparisonComparar valores de metavariables

Ejemplo avanzado: combinando operadores

rules:
- id: flask-open-redirect
patterns:
- pattern: redirect($URL)
- pattern-not: redirect("/...")
- pattern-inside: |
@app.route(...)
def $FUNC(...):
...
message: >
Posible open redirect — valida $URL antes de redirigir
languages: [python]
severity: ERROR
metadata:
cwe: "CWE-601"

Ejecutar reglas locales

# Archivo de reglas individual
semgrep --config=rules/my-rules.yaml src/

# Directorio de reglas
semgrep --config=rules/ src/

# Combinar reglas locales con registry
semgrep --config=rules/ --config=p/owasp-top-ten src/

Integración CI

GitHub Actions

name: Semgrep
on:
pull_request:
push: { branches: [ main ] }

jobs:
scan:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- name: Scan
run: |
mkdir -p reports
semgrep --config=auto --sarif -o reports/semgrep.sarif . || true
semgrep --config=auto --json -o reports/semgrep.json . || true
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: reports/semgrep.sarif }
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with: { name: semgrep-reports, path: reports/ }

Política de fallo

# Bloquear PR si hay findings de severidad ERROR
semgrep --config=auto --severity=ERROR --error .

El flag --error hace que Semgrep retorne exit code 1 si encuentra findings, útil para fallar el pipeline.

GitLab CI

semgrep:
image: semgrep/semgrep
script:
- semgrep --config=auto --sarif -o semgrep.sarif .
artifacts:
reports:
sast: semgrep.sarif

Ignorar findings

En código (inline)

# nosemgrep: mi-regla
result = eval(user_input)
// nosemgrep
const token = "hardcoded-for-testing";

Con .semgrepignore

Crear .semgrepignore en la raíz del repo (sintaxis similar a .gitignore):

# Ignorar tests
tests/
*_test.py
*.test.js

# Ignorar vendors / dependencias
vendor/
node_modules/
third_party/

# Ignorar archivos generados
*.generated.go
*.pb.go

Errores comunes

  • Demasiados false positives — empieza con p/ci que tiene reglas de bajo ruido, luego añade rulesets según necesites
  • Escaneo lento — usa --exclude para ignorar directorios grandes (vendors, node_modules) y --max-target-bytes para archivos pesados
  • Regla no encuentra nada — verifica el lenguaje con --lang y prueba el patrón en semgrep.dev/playground
  • --config=auto requiere login — en versiones recientes necesitas semgrep login o usar rulesets específicos como --config=p/ci

Referencias