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:

  1. 3 jobs en paralelo (dependency-scan, sast-scan, secrets-scan) — herramientas independientes corren simultáneamente
  2. Claude Code después consumiendo contexto de los anteriores (evita duplicar findings que ya detectó otra herramienta)
  3. 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:

  1. pip-audit (o npm audit) en CI
  2. Bandit (o ESLint security)
  3. gitleaks
  4. 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:

  1. Lee outputs de 3 herramientas
  2. Normaliza al schema unificado
  3. Deduplica por (path, line, category)
  4. Genera reporte final

Ejercicio 3: Claude con contexto (Difícil)

Modifica el scan de Claude para:

  1. Leer findings de pip-audit y bandit
  2. Pasarlos como contexto en el prompt
  3. Pedirle que solo reporte lo nuevo
  4. 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

  1. Snyk — Scanner de dependencias y código
  2. pip-audit — PyPA's vulnerability scanner
  3. Bandit — Python SAST
  4. Semgrep — Multi-lenguaje SAST con reglas custom
  5. gitleaks — Secrets detection
  6. GitHub Advanced Security — Suite oficial de GitHub
  7. SARIF Standard — Formato estándar para findings de seguridad