import sys
from pathlib import Path
import json
import pandas as pd
# Agregar el directorio scripts al path para importar el modulo generate_codeql
# Este módulo contiene la clase CodeQLAnalyzer que orquesta todo el análisis
project_root = next(p for p in [Path.cwd(), *Path.cwd().parents] if (p / 'pyproject.toml').exists())
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_codeql.py")Análisis de Seguridad con CodeQL
Este notebook ejecuta un análisis de seguridad estático sobre los repositorios clonados en data/repos/ utilizando CodeQL, la herramienta de análisis de código de GitHub.
¿Qué es CodeQL?
CodeQL es un motor de búsqueda de código que permite escribir consultas para encontrar vulnerabilidades, problemas de seguridad y defectos de codificación en el código fuente. Produce resultados en formato SARIF (Static Analysis Results Interchange Format).
Flujo de Ejecución
- Descubrimiento de repositorios: Escanea
data/repos/para encontrar todos los subdirectorios - Creación de base de datos CodeQL: Para cada repositorio, CodeQL crea una base de datos indexada del código
- Análisis: Se ejecutan consultas de seguridad predefinidas en la base de datos
- Generación de resultados: Los hallazgos se convierten a formato JSON normalizado y se guardan en
data/results/
Preparación: Importar scripts/generate_codeql.py
A continuación importaremos el módulo CodeQLAnalyzer desde el archivo scripts/generate_codeql.py.
# Importar la clase CodeQLAnalyzer desde generate_codeql.py
from generate_codeql import CodeQLAnalyzer
print("✓ Importado correctamente: CodeQLAnalyzer desde scripts/generate_codeql.py")Configuración
# Configurar rutas
repos_path = project_root / "data" / "repos"
output_path = project_root / "data" / "results"
# Crear analizador
analizador = CodeQLAnalyzer(
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}")Ejecución: Análisis CodeQL (usando scripts/generate_codeql.py)
Ahora ejecutaremos el análisis CodeQL sobre todos los repositorios descubiertos. El script generate_codeql.py manejará automáticamente la orquestación completa.
# Descubrir repositorios usando el método del CodeQLAnalyzer
# Este método viene de scripts/generate_codeql.py
repositorios = analizador.discover_repositories()
print(f"Repositorios encontrados: {len(repositorios)}")
for repo in repositorios:
print(f" - {repo}")print("Iniciando análisis CodeQL con scripts/generate_codeql.py...")
analizador.run()
print("✓ Análisis completado")Inspección de Resultados
# Listar archivos de resultados generados
output_path = project_root / "data" / "results"
codeql_files = sorted(output_path.glob("*-codeql.json"))
print(f"📁 Buscando en: {output_path}")
print(f"✓ Archivos de análisis CodeQL encontrados: {len(codeql_files)}")
for archivo in codeql_files:
tamaño = archivo.stat().st_size / 1024 # KB
print(f" - {archivo.name} ({tamaño:.1f} KB)")# Cargar y mostrar resumen de UN análisis
results_dir = project_root / "data" / "results"
results_files = sorted(results_dir.glob("*-codeql.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('-codeql', '')
total_found = data_content.get('total_issues', 0)
print(f"\n📊 ANÁLISIS: {repo_name_clean}")
print(f"{'='*50}")
print(f"✓ Total de problemas encontrados: {total_found}")
if total_found > 0:
severity_data = data_content.get('issues_by_severity', {})
print(f"\n🔍 Problemas por severidad:")
for severity_type, count in severity_data.items():
if count > 0:
emoji = "🔴" if severity_type == "error" else "🟡" if severity_type == "warning" else "⚪"
print(f" {emoji} {severity_type}: {count}")
else:
print("⚠️ No se encontraron problemas")
else:
print("❌ No se encontraron archivos de análisis CodeQL")# Mostrar problemas detallados
output_path_det = project_root / "data" / "results"
codeql_files_det = sorted(output_path_det.glob("*-codeql.json"))
if codeql_files_det:
archivo_det = codeql_files_det[0]
with open(archivo_det, 'r', encoding='utf-8') as f:
analisis_det = json.load(f)
issues_list = analisis_det.get('issues', [])
print(f"📋 Leyendo: {archivo_det.name}")
print(f"📊 Total de issues en archivo: {len(issues_list)}")
if issues_list:
# Crear DataFrame con los problemas
df_issues = pd.DataFrame(issues_list)
# Seleccionar y reordenar columnas
cols_mostrar = ['rule_id', 'level', 'message', 'file']
cols_disponibles = [c for c in cols_mostrar if c in df_issues.columns]
df_mostrar = df_issues[cols_disponibles].head(20)
print(f"\n📋 Primeros 20 problemas encontrados:")
print(f"{'='*100}")
for idx, row in df_mostrar.iterrows():
print(f"\n{idx+1}. [{row['level'].upper()}] {row['rule_id']}")
print(f" 📝 {row['message'][:80]}")
if 'file' in row and pd.notna(row['file']):
print(f" 📂 {row['file']}")
if len(issues_list) > 20:
print(f"\n... y {len(issues_list)-20} problemas más")
else:
print("⚠️ No hay detalles de problemas en el archivo")Análisis Detallado de Problemas
# Compilar estadísticas consolidadas
from collections import defaultdict
output_path_stats = project_root / "data" / "results"
codeql_files_stats = sorted(output_path_stats.glob("*-codeql.json"))
stats_consolidadas = {
'total_repositories': len(codeql_files_stats),
'total_issues': 0,
'issues_by_repo': {},
'issues_by_severity': defaultdict(int),
'top_rules': defaultdict(int)
}
for archivo in codeql_files_stats:
with open(archivo, 'r', encoding='utf-8') as f:
data = json.load(f)
repo_name = archivo.stem.replace('-codeql', '')
total = data.get('total_issues', 0)
stats_consolidadas['total_issues'] += total
stats_consolidadas['issues_by_repo'][repo_name] = total
# Contar por severidad
for severity, count in data.get('issues_by_severity', {}).items():
stats_consolidadas['issues_by_severity'][severity] += count
# Contar top rules
for issue in data.get('issues', []):
rule = issue.get('rule_id', 'unknown')
stats_consolidadas['top_rules'][rule] += 1
# Convertir defaultdicts a dicts para visualización
stats_consolidadas['issues_by_severity'] = dict(stats_consolidadas['issues_by_severity'])
stats_consolidadas['top_rules'] = dict(sorted(
stats_consolidadas['top_rules'].items(),
key=lambda x: x[1],
reverse=True
)[:10])
print("\n📊 ESTADÍSTICAS CONSOLIDADAS DE CODEQL")
print(f"{'='*60}")
print(f"\n📁 Repositorios analizados: {stats_consolidadas['total_repositories']}")
print(f"🔴 Total de problemas: {stats_consolidadas['total_issues']}")
print(f"\n📋 Problemas por repositorio:")
for repo, count in stats_consolidadas['issues_by_repo'].items():
print(f" • {repo}: {count}")
print(f"\n⚠️ Problemas por severidad:")
for severity, count in sorted(stats_consolidadas['issues_by_severity'].items()):
emoji = "🔴" if severity == "error" else "🟡" if severity == "warning" else "⚪"
print(f" {emoji} {severity}: {count}")
print(f"\n🎯 Top 10 reglas más comunes:")
for rule, count in list(stats_consolidadas['top_rules'].items())[:10]:
print(f" • {rule}: {count}")Próximos Pasos
Los resultados del análisis CodeQL se han guardado en data/results/ con el patrón {repo-name}-codeql.json.
Cómo usar estos resultados:
- Revisar vulnerabilidades críticas: Filtrar por
level: "error"para identificar problemas de seguridad críticos - Seguimiento: Integrar con sistemas de seguimiento de mermas (issues, PRs) para gestionar remediación
- Análisis de tendencias: Comparar resultados entre repositorios y períodos de tiempo
- Automatizar: Integrar estos análisis en CI/CD para detectar problemas continuamente
Para agregar nuevos repositorios:
- Edita
data/repos.jsony agrega nuevos repositorios:
{
"repositories": [
{
"url": "https://github.com/owner/repo-name.git",
"path": "data/repos/repo-name",
"ref": "v1.0.0"
}
]
}- Ejecuta en la terminal (esto clonará los repositorios y los agregará como submódulos):
uv run python scripts/add_submodules.py- Ejecuta el análisis nuevamente desde el notebook o la terminal:
uv run scripts/generate_codeql.py