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​