Módulo 5: Security Scanning y Rollback Inteligente
Integración con Herramientas de Seguridad Existentes
Integración con Herramientas de Seguridad Existentes
Descripción
Claude Code es bueno detectando patrones de seguridad contextuales (cápsula 02), pero no es la única herramienta que necesitas. Snyk detecta CVEs en dependencias mejor que cualquier modelo. Dependabot crea PRs automáticos para actualizar paquetes vulnerables. npm audit y pip-audit corren localmente y son gratis. Cada herramienta tiene su sweet spot — el patrón profesional es orquestarlas todas, no elegir una.
Esta cápsula te enseña a integrar Claude Code con el ecosistema de herramientas de seguridad: cuándo usar cada una, cómo combinar sus outputs sin duplicación, y cómo construir un pipeline donde cada herramienta cubre su zona de fortaleza.
Al terminar, vas a tener un workflow que combina 3-4 herramientas de seguridad complementarias, cada una corriendo en paralelo donde es eficiente, agregando resultados en un único reporte coherente.
El Mapa de Herramientas
┌──────────────────────────────────────────────────────────┐
│ DEPENDENCY VULNERABILITY SCANNERS │
│ ✅ CVEs en paquetes / versiones │
│ ❌ NO cubren código propio │
│ │
│ → Snyk, Dependabot, npm audit, pip-audit, gemnasium │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ STATIC ANALYSIS (SAST) — REGLAS │
│ ✅ Patterns conocidos en código (SQL inj, XSS) │
│ ❌ No detectan logic bugs ni patrones contextuales │
│ │
│ → Semgrep, Bandit (Python), ESLint security plugins, │
│ SonarQube │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ SECRETS DETECTION │
│ ✅ API keys, tokens, secrets en código │
│ ❌ Solo detecta lo que ya filtraste — no previene │
│ │
│ → gitleaks, TruffleHog, GitHub Secret Scanning │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ CLAUDE CODE (este path) │
│ ✅ Patterns contextuales que reglas estáticas no detectan│
│ ✅ Explicación del finding y remediation │
│ ❌ Más lento y caro que herramientas estáticas │
│ │
│ → Análisis con prompt especializado (cápsula 02) │
└──────────────────────────────────────────────────────────┘
La regla: cada herramienta tiene una zona donde es la mejor opción. Combinarlas eficientemente es el patrón profesional.
Stack Recomendado por Lenguaje
Python
Layer 1 (rápido, gratis):
- pip-audit → CVEs en dependencias
- bandit → SAST estático
- gitleaks → secrets
Layer 2 (más profundo, paid):
- Snyk → mejor base de datos de CVEs
Layer 3 (contextual):
- Claude Code → patrones que el resto no detecta
JavaScript / TypeScript
Layer 1:
- npm audit → CVEs en dependencias
- ESLint con → SAST estático
security plugin
- gitleaks → secrets
Layer 2:
- Snyk → mejor base de CVEs
- Semgrep → reglas avanzadas
Layer 3:
- Claude Code → patrones contextuales
Go / Java / Ruby
Patrones similares — herramientas específicas del lenguaje + Claude Code para profundidad contextual.
El Pipeline Combinado
# .github/workflows/security-pipeline.yml
name: Security Pipeline
on:
pull_request:
types: [opened, synchronize]
schedule:
- cron: '0 6 * * *' # daily 6am UTC para scan completo
permissions:
contents: read
pull-requests: write
security-events: write
jobs:
# ============================================
# JOB 1: Dependency vulnerabilities
# ============================================
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install pip-audit
- name: pip-audit
run: |
pip-audit --format json --output pip-audit-results.json || true
# || true para no fallar el job aunque haya findings
- name: Snyk Open Source
uses: snyk/actions/python@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --json-file-output=snyk-results.json --severity-threshold=high
continue-on-error: true
- uses: actions/upload-artifact@v4
if: always()
with:
name: dependency-results
path: |
pip-audit-results.json
snyk-results.json
# ============================================
# JOB 2: SAST (Static Application Security Testing)
# ============================================
sast-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install bandit
- name: Bandit
run: |
bandit -r src/ -f json -o bandit-results.json || true
- name: Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: p/security-audit p/owasp-top-ten
continue-on-error: true
- uses: actions/upload-artifact@v4
if: always()
with:
name: sast-results
path: |
bandit-results.json
semgrep-results.json
# ============================================
# JOB 3: Secrets detection
# ============================================
secrets-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# ============================================
# JOB 4: Claude Code contextual scan
# ============================================
claude-scan:
runs-on: ubuntu-latest
needs: [dependency-scan, sast-scan] # ← consume outputs de jobs anteriores
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install anthropic requests
- uses: actions/download-artifact@v4
with:
name: dependency-results
- uses: actions/download-artifact@v4
with:
name: sast-results
- name: Run Claude Code scan with context from other tools
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: python scripts/security_scan_with_context.py
# ============================================
# JOB 5: Aggregate y publicar
# ============================================
aggregate:
runs-on: ubuntu-latest
needs: [dependency-scan, sast-scan, secrets-scan, claude-scan]
if: always() # corre incluso si jobs anteriores fallan
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11' }
- run: pip install requests
- uses: actions/download-artifact@v4
with: { path: artifacts/ }
- name: Aggregate findings
run: python scripts/aggregate_security_findings.py
- name: Post to PR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: python scripts/post_aggregated_findings.py
El patrón:
- 3 jobs en paralelo (dependency-scan, sast-scan, secrets-scan) — herramientas independientes corren simultáneamente
- Claude Code después consumiendo contexto de los anteriores (evita duplicar findings que ya detectó otra herramienta)
- Aggregate al final combina todo en un único reporte
Claude Code con Contexto
El truco para evitar duplicación: pasarle a Claude los findings de las otras herramientas como contexto, y pedirle que solo reporte patterns que ellas no detectaron.
"""scripts/security_scan_with_context.py
Claude Code scan que evita duplicar findings de otras herramientas.
"""
import json
import os
import sys
from pathlib import Path
from anthropic import Anthropic
def load_other_findings() -> dict:
"""Cargar findings de otras herramientas para contexto."""
findings = {}
# pip-audit
if Path("pip-audit-results.json").exists():
try:
data = json.loads(Path("pip-audit-results.json").read_text())
findings["dependency_cves"] = [
f"{vuln['name']} {vuln['version']} → {vuln['id']}"
for v in data.get("dependencies", [])
for vuln in v.get("vulns", [])
]
except Exception:
findings["dependency_cves"] = []
# Bandit
if Path("bandit-results.json").exists():
try:
data = json.loads(Path("bandit-results.json").read_text())
findings["sast_findings"] = [
f"{r['filename']}:{r['line_number']} — {r['issue_text']}"
for r in data.get("results", [])
]
except Exception:
findings["sast_findings"] = []
return findings
def build_prompt_with_context(diff: str, other_findings: dict) -> str:
"""Prompt que incluye contexto de otras herramientas."""
other_summary = "Otras herramientas ya reportaron:\n"
if other_findings.get("dependency_cves"):
other_summary += f"\n- CVEs en dependencias ({len(other_findings['dependency_cves'])} encontrados):\n"
for cve in other_findings["dependency_cves"][:10]:
other_summary += f" • {cve}\n"
if other_findings.get("sast_findings"):
other_summary += f"\n- Findings SAST ({len(other_findings['sast_findings'])} encontrados):\n"
for finding in other_findings["sast_findings"][:10]:
other_summary += f" • {finding}\n"
return f"""Eres un auditor de seguridad. Otras herramientas (pip-audit, bandit) ya
escanearon este código. Tu tarea es detectar **patrones contextuales** que esas
herramientas no detectaron — bugs lógicos, validation faltante con context,
auth checks mal aplicados, IDOR, etc.
{other_summary}
NO REPORTES:
- CVEs en dependencias (ya cubierto por pip-audit/Snyk)
- Issues que las herramientas SAST ya reportaron
- Patrones triviales que herramientas estáticas detectan
REPORTA SOLO:
- Lógica de auth/authz incorrecta
- IDOR (Insecure Direct Object References)
- Validation faltante en flujos complejos
- Patrones de inyección que requieren contexto multi-archivo
- Race conditions en código concurrente
- Issues que requieren entender la lógica de negocio
OUTPUT: JSON {{"findings": [...]}} con la estructura estándar.
DIFF:
{diff[:30000]}
"""
def main() -> int:
diff_file = Path("filtered_diff.txt")
if not diff_file.exists() or not diff_file.read_text().strip():
Path("claude_findings.json").write_text(json.dumps({"findings": []}))
return 0
other_findings = load_other_findings()
diff = diff_file.read_text()
client = Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=4000,
messages=[{
"role": "user",
"content": build_prompt_with_context(diff, other_findings),
}],
)
text = response.content[0].text.strip()
if text.startswith("```"):
text = "\n".join(text.split("\n")[1:-1])
try:
data = json.loads(text)
except json.JSONDecodeError:
data = {"findings": []}
Path("claude_findings.json").write_text(json.dumps(data, indent=2))
print(f"Claude detectó {len(data.get('findings', []))} contextual findings")
return 0
if __name__ == "__main__":
sys.exit(main())
Aggregator: Un Único Reporte
"""scripts/aggregate_security_findings.py
Combina findings de todas las herramientas en un reporte unificado.
"""
import json
from pathlib import Path
from typing import TypedDict
class UnifiedFinding(TypedDict):
source: str # "pip-audit", "bandit", "snyk", "semgrep", "claude"
severity: str # "critical", "high", "medium", "low"
category: str
path: str
line: int
title: str
description: str
remediation: str
def normalize_pip_audit(data: dict) -> list[UnifiedFinding]:
findings = []
for dep in data.get("dependencies", []):
for vuln in dep.get("vulns", []):
findings.append({
"source": "pip-audit",
"severity": map_cve_severity(vuln.get("id", "")),
"category": "dependencies",
"path": "requirements.txt",
"line": 1,
"title": f"{dep['name']} {dep['version']} has {vuln['id']}",
"description": vuln.get("description", ""),
"remediation": f"Upgrade to {vuln.get('fix_versions', ['latest'])[0]}",
})
return findings
def normalize_bandit(data: dict) -> list[UnifiedFinding]:
findings = []
severity_map = {"HIGH": "high", "MEDIUM": "medium", "LOW": "low"}
for r in data.get("results", []):
findings.append({
"source": "bandit",
"severity": severity_map.get(r.get("issue_severity", "LOW"), "low"),
"category": "sast",
"path": r.get("filename", ""),
"line": r.get("line_number", 0),
"title": r.get("test_name", ""),
"description": r.get("issue_text", ""),
"remediation": r.get("issue_cwe", {}).get("link", ""),
})
return findings
def normalize_claude(data: dict) -> list[UnifiedFinding]:
findings = []
for f in data.get("findings", []):
findings.append({
"source": "claude",
"severity": f.get("severity", "low"),
"category": f.get("category", "contextual"),
"path": f.get("path", ""),
"line": f.get("line", 0),
"title": f.get("title", ""),
"description": f.get("description", ""),
"remediation": f.get("remediation", ""),
})
return findings
def map_cve_severity(cve_id: str) -> str:
"""Mapear CVE ID a severity (simplificado)."""
# En producción real, consultar la API de NVD para CVSS score
return "high" # default conservador
def deduplicate(findings: list[UnifiedFinding]) -> list[UnifiedFinding]:
"""Eliminar duplicados (misma path + line + category)."""
seen = set()
unique = []
for f in findings:
key = (f["path"], f["line"], f["category"])
if key not in seen:
seen.add(key)
unique.append(f)
return unique
def main() -> int:
all_findings: list[UnifiedFinding] = []
# pip-audit
pip_audit_path = Path("artifacts/dependency-results/pip-audit-results.json")
if pip_audit_path.exists():
all_findings.extend(normalize_pip_audit(json.loads(pip_audit_path.read_text())))
# Bandit
bandit_path = Path("artifacts/sast-results/bandit-results.json")
if bandit_path.exists():
all_findings.extend(normalize_bandit(json.loads(bandit_path.read_text())))
# Claude
claude_path = Path("artifacts/claude-findings/claude_findings.json")
if claude_path.exists():
all_findings.extend(normalize_claude(json.loads(claude_path.read_text())))
# Deduplicate
unique = deduplicate(all_findings)
# Stats
by_severity = {"critical": 0, "high": 0, "medium": 0, "low": 0}
by_source = {}
for f in unique:
by_severity[f["severity"]] = by_severity.get(f["severity"], 0) + 1
by_source[f["source"]] = by_source.get(f["source"], 0) + 1
report = {
"findings": unique,
"summary": {
"total": len(unique),
"by_severity": by_severity,
"by_source": by_source,
},
}
Path("aggregated_security_report.json").write_text(json.dumps(report, indent=2))
print(f"Aggregated: {len(unique)} unique findings")
print(f" By severity: {by_severity}")
print(f" By source: {by_source}")
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
Cuándo Usar Cada Herramienta
SOLO PARA ALGUNAS COSAS:
✅ pip-audit / npm audit:
- Gratis, rápido
- Cubre solo dependencies
- Úsalo SIEMPRE como primera línea
✅ Snyk:
- Mejor base de datos de CVEs
- Pago, pero free tier para open source
- Vale para equipos serios
✅ Bandit / Semgrep:
- SAST estático, gratis
- Detecta patterns conocidos rápido
- Úsalo SIEMPRE para Python/JS
✅ gitleaks:
- Secrets detection en pre-commit y CI
- Gratis
- Úsalo SIEMPRE
✅ GitHub Secret Scanning:
- Gratis para repos públicos
- Detecta tokens conocidos automáticamente
- Actívalo si está disponible
✅ Claude Code (este path):
- Patrones contextuales y lógicos
- Caro pero profundo
- Como complemento, no como reemplazo
El stack típico recomendado:
LAYER 1 (siempre, gratis): pip-audit + bandit + gitleaks
LAYER 2 (si presupuesto): Snyk
LAYER 3 (siempre, $): Claude Code para context
Trampas Comunes
Error 1: Confiar solo en una herramienta
Síntoma: Todo es Claude Code, sin scanners estáticos. O todo es Snyk, sin Claude.
Por qué pasa: Simplificar pareció buena idea.
Cómo corregir: Cada herramienta tiene gaps. Combinarlas cubre los gaps mutuos.
Error 2: Findings duplicados sin deduplicar
Síntoma: El reporte tiene "SQL injection en payment.py:42" 3 veces (Bandit, Semgrep, Claude).
Por qué pasa: No hay aggregator que deduplique.
Cómo corregir: Aggregator con deduplicación por (path, line, category) como muestra el script.
Error 3: Claude reporta lo que Bandit ya reportó
Síntoma: Claude duplica findings de SAST estático, gastando tokens en lo que herramientas más baratas ya cubrieron.
Por qué pasa: Prompt sin contexto de otros findings.
Cómo corregir: Pasar findings de otras herramientas como contexto (script security_scan_with_context.py). Pedirle que solo reporte lo nuevo.
Error 4: Severity inconsistente entre herramientas
Síntoma: Bandit dice "MEDIUM", Snyk dice "HIGH", Claude dice "CRITICAL" para el mismo issue.
Por qué pasa: Cada herramienta tiene su escala.
Cómo corregir: Normalizar al mismo schema (critical/high/medium/low). El aggregator hace el mapping.
Error 5: No correr scans periódicos
Síntoma: Solo escaneas en PRs. CVEs nuevos en dependencias actuales no se detectan hasta el próximo PR.
Por qué pasa: Trigger solo en pull_request.
Cómo corregir: Agregar trigger schedule: para correr daily/weekly contra main. Detecta CVEs nuevos en dependencias estables.
Diagnóstico
Pregunta 1: ¿Cuántas herramientas de seguridad tienes en tu pipeline actual?
1 = gaps. 3-5 = stack profesional. 10+ = posible over-engineering.
Pregunta 2: ¿Tu pipeline corre dependency scan, SAST, secrets, y context analysis?
Las 4 categorías. Si falta alguna, vas a tener gaps específicos.
Pregunta 3: ¿Tu Claude Code scan tiene contexto de las otras herramientas?
Sin contexto, duplica findings y desperdicia tokens. Con contexto, agrega valor.
Pregunta 4: ¿Tienes deduplicación entre herramientas?
Sin deduplicación, el equipo ve "el mismo issue" varias veces y pierde confianza en el bot.
Pregunta 5: ¿Corres scans periódicos contra main, no solo en PRs?
CVEs nuevos en dependencias estables solo se detectan con scans periódicos.
Ejercicios
Ejercicio 1: Stack mínimo (Fácil)
Configura en tu repo:
- pip-audit (o npm audit) en CI
- Bandit (o ESLint security)
- gitleaks
- Claude Code (de cápsula 02)
Verifica que los 4 jobs corren en paralelo en cada PR.
Ejercicio 2: Aggregator con deduplicación (Medio)
Implementa aggregate_security_findings.py que:
- Lee outputs de 3 herramientas
- Normaliza al schema unificado
- Deduplica por (path, line, category)
- Genera reporte final
Ejercicio 3: Claude con contexto (Difícil)
Modifica el scan de Claude para:
- Leer findings de pip-audit y bandit
- Pasarlos como contexto en el prompt
- Pedirle que solo reporte lo nuevo
- Comparar findings con/sin contexto — ¿hay diferencia en la cantidad?
Resumen
- Cada herramienta tiene su zona de fortaleza — combinarlas es el patrón profesional
- 3 layers típicos: dependency vulnerabilities, SAST, secrets, + Claude Code para contexto
- Claude con contexto evita duplicar findings de otras herramientas
- Aggregator unificado normaliza schemas y deduplica
- Severity homogénea después de normalización (critical/high/medium/low)
- Scans periódicos contra main detectan CVEs nuevos en dependencias estables
- No reemplazar — complementar: Claude Code agrega lo que herramientas estáticas no pueden
Próxima cápsula: 04 — Rollback automático con triggers. Tienes security scanning pre-merge robusto. Pero algunos issues solo aparecen en producción. La cápsula 04 cubre el safety net post-deploy: rollback automático cuando las métricas se degradan.
Recursos Adicionales
- Snyk — Scanner de dependencias y código
- pip-audit — PyPA's vulnerability scanner
- Bandit — Python SAST
- Semgrep — Multi-lenguaje SAST con reglas custom
- gitleaks — Secrets detection
- GitHub Advanced Security — Suite oficial de GitHub
- SARIF Standard — Formato estándar para findings de seguridad