Análisis de Vulnerabilidades con Grype

Preparación: Importar scripts/generate_grype.py

A continuación importaremos el módulo GrypeAnalyzer desde el archivo scripts/generate_grype.py.

import sys
from pathlib import Path
import json
import pandas as pd

# Agregar el directorio scripts al path para importar el modulo generate_grype
# Este módulo contiene la clase GrypeAnalyzer que orquesta todo el análisis
project_root = Path().cwd().parent.parent
scripts_path = project_root / "scripts"
if str(scripts_path) not in sys.path:
    sys.path.insert(0, str(scripts_path))

print(f"Notebook working directory: {Path().cwd()}")
print(f"Project root: {project_root}")
print(f"Scripts path: {scripts_path}")
print(f"Importing from: generate_grype.py")
Notebook working directory: /workspaces/id_2026/nbs/vuln
Project root: /workspaces/id_2026
Scripts path: /workspaces/id_2026/scripts
Importing from: generate_grype.py
# Importar la clase GrypeAnalyzer desde generate_grype.py
from generate_grype import GrypeAnalyzer

print("✓ Importado correctamente: GrypeAnalyzer desde scripts/generate_grype.py")
✓ Importado correctamente: GrypeAnalyzer desde scripts/generate_grype.py

Configuración

# Configurar rutas
repos_path = project_root / "data" / "repos"
output_path = project_root / "data" / "results"

# Crear analizador
analizador = GrypeAnalyzer(
    repos_path=str(repos_path),
    output_path=str(output_path)
)

# Configurar para ejecución normal (no dry-run)
analizador.dry_run = False

print(f"Repos path: {repos_path}")
print(f"Output path: {output_path}")
print(f"Dry run: {analizador.dry_run}")
Repos path: /workspaces/id_2026/data/repos
Output path: /workspaces/id_2026/data/results
Dry run: False

Ejecución: Análisis de Vulnerabilidades (usando scripts/generate_grype.py)

Ahora ejecutaremos el análisis de vulnerabilidades sobre todos los repositorios descubiertos. El script generate_grype.py manejará automáticamente la orquestación completa.

# Descubrir repositorios usando el método del GrypeAnalyzer
# Este método viene de scripts/generate_grype.py
repositorios = analizador.discover_repositories()
print(f"Repositorios encontrados: {len(repositorios)}")
for repo in repositorios:
    print(f"  - {repo}")
Repositorios encontrados: 3
  - data/repos/claude-code-action
  - data/repos/genai-code-review
  - data/repos/opencode
print("Iniciando análisis de vulnerabilidades con scripts/generate_grype.py...")
analizador.run()
print("✓ Análisis completado")
INFO | === Grype Environment Diagnostics ===
INFO | Grype CLI: Application:         grype
Version:             0.111.0
BuildDate:           2026-04-09T13:14:56Z
GitCommit:           1f19355a7ee2d7e2bd58da6255bdeb618eb0c0d1
GitDescription:      v0.111.0
Platform:            linux/amd64
GoVersion:           go1.25.9
Compiler:            gc
Syft Version:        v1.42.4
Supported DB Schema: 6
Iniciando análisis de vulnerabilidades con scripts/generate_grype.py...
INFO | Grype DB status: Path:      /home/vscode/.cache/grype/db/6/vulnerability.db
Schema:    v6.1.4
Built:     2026-04-11T06:33:07Z
From:      https://grype.anchore.io/databases/v6/vulnerability-db_v6.1.4_2026-04-11T00:33:16Z_1775889187.tar.zst?checksum=sha256%3Adc346c1a349a2a05bb697264987bca2607deb8ce3795bd3c07d8bc43996be21b
Status:    valid
INFO | === Environment Check Complete ===

INFO | [1/3] Scanning data/repos/claude-code-action with Grype...
INFO | Manifests found in claude-code-action: package-lock.json, package.json
INFO | Running Grype on claude-code-action...
INFO | Parsing Grype output...
INFO | Found 5 matches in Grype output
INFO | Parsed 5 vulnerabilities total
INFO | Raw Grype output saved to data/results/claude-code-action-grype-raw.json
INFO | Normalized analysis saved to data/results/claude-code-action-grype.json
INFO | [2/3] Scanning data/repos/genai-code-review with Grype...
INFO | Manifests found in genai-code-review: requirements.txt
INFO | Running Grype on genai-code-review...
INFO | Parsing Grype output...
INFO | Found 3 matches in Grype output
INFO | Parsed 3 vulnerabilities total
INFO | Raw Grype output saved to data/results/genai-code-review-grype-raw.json
INFO | Normalized analysis saved to data/results/genai-code-review-grype.json
INFO | [3/3] Scanning data/repos/opencode with Grype...
INFO | Manifests found in opencode: Cargo.lock, Cargo.toml, package.json
INFO | Running Grype on opencode...
INFO | Parsing Grype output...
INFO | Found 9 matches in Grype output
INFO | Parsed 9 vulnerabilities total
INFO | Raw Grype output saved to data/results/opencode-grype-raw.json
INFO | Normalized analysis saved to data/results/opencode-grype.json
INFO | Summary | total_repos=3 | repos_scanned=3 | files_generated=6 | skipped=0 | errors=0
✓ Análisis completado

Inspección de Resultados

# Listar archivos de resultados generados
output_path = project_root / "data" / "results"
grype_files = sorted(output_path.glob("*-grype.json"))
grype_raw_files = sorted(output_path.glob("*-grype-raw.json"))

print(f"📁 Buscando en: {output_path}")
print(f"✓ Archivos de análisis Grype (normalizados): {len(grype_files)}")
for archivo in grype_files:
    tamaño = archivo.stat().st_size / 1024  # KB
    print(f"  - {archivo.name} ({tamaño:.1f} KB)")

print(f"\n✓ Archivos de análisis Grype (raw): {len(grype_raw_files)}")
for archivo in grype_raw_files:
    tamaño = archivo.stat().st_size / 1024  # KB
    print(f"  - {archivo.name} ({tamaño:.1f} KB)")
📁 Buscando en: /workspaces/id_2026/data/results
✓ Archivos de análisis Grype (normalizados): 3
  - claude-code-action-grype.json (2.1 KB)
  - genai-code-review-grype.json (1.3 KB)
  - opencode-grype.json (3.4 KB)

✓ Archivos de análisis Grype (raw): 3
  - claude-code-action-grype-raw.json (22.4 KB)
  - genai-code-review-grype-raw.json (15.7 KB)
  - opencode-grype-raw.json (30.8 KB)
# Cargar y mostrar resumen de UN análisis
results_dir = project_root / "data" / "results"
results_files = sorted(results_dir.glob("*-grype.json"))

print(f"📂 Buscando en: {results_dir}")
print(f"📋 Archivos encontrados: {len(results_files)}")

if results_files:
    result_file = results_files[0]
    print(f"📖 Leyendo: {result_file.name}")
    
    with open(result_file, 'r', encoding='utf-8') as f:
        data_content = json.load(f)
    
    repo_name_clean = result_file.stem.replace('-grype', '')
    total_found = data_content.get('total_vulnerabilities', 0)
    
    print(f"\n📊 ANÁLISIS: {repo_name_clean}")
    print(f"{'='*50}")
    print(f"✓ Total de vulnerabilidades encontradas: {total_found}")
    
    if total_found > 0:
        severity_data = data_content.get('vulnerabilities_by_severity', {})
        print(f"\n🔍 Vulnerabilidades por severidad:")
        for severity_type, count in severity_data.items():
            if count > 0:
                emoji = "🔴" if severity_type == "critical" else "🟠" if severity_type == "high" else "🟡" if severity_type == "medium" else "⚪"
                print(f"  {emoji} {severity_type}: {count}")
    else:
        print("⚠️  No se encontraron vulnerabilidades")
else:
    print("❌ No se encontraron archivos de análisis Grype")
📂 Buscando en: /workspaces/id_2026/data/results
📋 Archivos encontrados: 3
📖 Leyendo: claude-code-action-grype.json

📊 ANÁLISIS: claude-code-action
==================================================
✓ Total de vulnerabilidades encontradas: 5

🔍 Vulnerabilidades por severidad:
  ⚪ low: 5
# Mostrar vulnerabilidades detalladas
output_path_det = project_root / "data" / "results"
grype_files_det = sorted(output_path_det.glob("*-grype.json"))

if grype_files_det:
    archivo_det = grype_files_det[0]
    
    with open(archivo_det, 'r', encoding='utf-8') as f:
        analisis_det = json.load(f)
    
    vulnerabilities_list = analisis_det.get('vulnerabilities', [])
    
    print(f"📋 Leyendo: {archivo_det.name}")
    print(f"📊 Total de vulnerabilidades en archivo: {len(vulnerabilities_list)}")
    
    if vulnerabilities_list:
        # Crear DataFrame con las vulnerabilidades
        df_vulns = pd.DataFrame(vulnerabilities_list)
        
        # Seleccionar y reordenar columnas
        cols_mostrar = ['package_name', 'current_version', 'vuln_id', 'vuln_severity', 'fix_version']
        cols_disponibles = [c for c in cols_mostrar if c in df_vulns.columns]
        df_mostrar = df_vulns[cols_disponibles].head(20)
        
        print(f"\n📋 Primeras 20 vulnerabilidades encontradas:")
        print(f"{'='*120}")
        for idx, row in df_mostrar.iterrows():
            print(f"\n{idx+1}. [{row['vuln_severity'].upper()}] {row['vuln_id']}")
            print(f"   📦 {row['package_name']} (actual: {row['current_version']})")
            if pd.notna(row['fix_version']) and row['fix_version'] != 'N/A':
                print(f"   ✓ Fix disponible en: {row['fix_version']}")
        
        if len(vulnerabilities_list) > 20:
            print(f"\n... y {len(vulnerabilities_list)-20} vulnerabilidades más")
    else:
        print("⚠️  No hay detalles de vulnerabilidades en el archivo")
📋 Leyendo: claude-code-action-grype.json
📊 Total de vulnerabilidades en archivo: 5

📋 Primeras 20 vulnerabilidades encontradas:
========================================================================================================================

1. [LOW] GHSA-v9p9-hfj2-hcw8
   📦 undici (actual: 5.29.0)

2. [LOW] GHSA-vrm6-8vpv-qv8q
   📦 undici (actual: 5.29.0)

3. [LOW] GHSA-g9mf-h72j-4rw9
   📦 undici (actual: 5.29.0)

4. [LOW] GHSA-2mjp-6q6p-2qxm
   📦 undici (actual: 5.29.0)

5. [LOW] GHSA-4992-7rv2-5pvq
   📦 undici (actual: 5.29.0)

Análisis Detallado de Vulnerabilidades

# Compilar estadísticas consolidadas
from collections import defaultdict

output_path_stats = project_root / "data" / "results"
grype_files_stats = sorted(output_path_stats.glob("*-grype.json"))

stats_consolidadas = {
    'total_repositories': len(grype_files_stats),
    'total_vulnerabilities': 0,
    'vulnerabilities_by_repo': {},
    'vulnerabilities_by_severity': defaultdict(int),
    'top_packages': defaultdict(int)
}

for archivo in grype_files_stats:
    with open(archivo, 'r', encoding='utf-8') as f:
        data = json.load(f)
    
    repo_name = archivo.stem.replace('-grype', '')
    total = data.get('total_vulnerabilities', 0)
    
    stats_consolidadas['total_vulnerabilities'] += total
    stats_consolidadas['vulnerabilities_by_repo'][repo_name] = total
    
    # Contar por severidad
    for severity, count in data.get('vulnerabilities_by_severity', {}).items():
        stats_consolidadas['vulnerabilities_by_severity'][severity] += count
    
    # Contar top packages con vulnerabilidades
    for vuln in data.get('vulnerabilities', []):
        pkg = vuln.get('package', 'unknown')
        stats_consolidadas['top_packages'][pkg] += 1

# Convertir defaultdicts a dicts para visualización
stats_consolidadas['vulnerabilities_by_severity'] = dict(stats_consolidadas['vulnerabilities_by_severity'])
stats_consolidadas['top_packages'] = dict(sorted(
    stats_consolidadas['top_packages'].items(), 
    key=lambda x: x[1], 
    reverse=True
)[:10])

print("\n📊 ESTADÍSTICAS CONSOLIDADAS DE GRYPE")
print(f"{'='*60}")
print(f"\n📁 Repositorios analizados: {stats_consolidadas['total_repositories']}")
print(f"🔴 Total de vulnerabilidades: {stats_consolidadas['total_vulnerabilities']}")

print(f"\n📋 Vulnerabilidades por repositorio:")
for repo, count in stats_consolidadas['vulnerabilities_by_repo'].items():
    print(f"  • {repo}: {count}")

print(f"\n⚠️  Vulnerabilidades por severidad:")
for severity, count in sorted(stats_consolidadas['vulnerabilities_by_severity'].items()):
    emoji = "🔴" if severity == "critical" else "🟠" if severity == "high" else "🟡" if severity == "medium" else "⚪"
    print(f"  {emoji} {severity}: {count}")

print(f"\n📦 Top 10 paquetes con vulnerabilidades:")
for pkg, count in list(stats_consolidadas['top_packages'].items())[:10]:
    print(f"  • {pkg}: {count}")

📊 ESTADÍSTICAS CONSOLIDADAS DE GRYPE
============================================================

📁 Repositorios analizados: 3
🔴 Total de vulnerabilidades: 17

📋 Vulnerabilidades por repositorio:
  • claude-code-action: 5
  • genai-code-review: 3
  • opencode: 9

⚠️  Vulnerabilidades por severidad:
  🔴 critical: 0
  🟠 high: 0
  ⚪ low: 17
  🟡 medium: 0

📦 Top 10 paquetes con vulnerabilidades:
  • unknown: 17

Próximos Pasos

Los resultados del análisis Grype se han guardado en data/results/ con los patrones: - {repo-name}-grype-raw.json: Salida original de Grype (útil para debug) - {repo-name}-grype.json: Formato normalizado (para análisis y visualización)

Cómo usar estos resultados:

  1. Revisar vulnerabilidades críticas: Filtrar por vuln_severity: "critical" para identificar problemas urgentes
  2. Seguimiento: Integrar con sistemas de seguimiento (issues, PRs) para gestionar remediación
  3. Análisis de tendencias: Comparar resultados entre repositorios y períodos de tiempo
  4. Actualizar dependencias: Usar fix_version para actualizar librerías vulnerables
  5. Automatizar: Integrar estos análisis en CI/CD para detectar problemas continuamente

Para agregar nuevos repositorios:

  1. Edita data/repos.json y agrega nuevos repositorios
  2. Ejecuta en la terminal:
uv run scripts/add_submodules.py
  1. Ejecuta el análisis nuevamente desde el notebook o la terminal:
uv run scripts/generate_grype.py