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

  1. Descubrimiento de repositorios: Escanea data/repos/ para encontrar todos los subdirectorios
  2. Creación de base de datos CodeQL: Para cada repositorio, CodeQL crea una base de datos indexada del código
  3. Análisis: Se ejecutan consultas de seguridad predefinidas en la base de datos
  4. 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.

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 = 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_codeql.py")
Notebook working directory: /workspaces/id_2026/nbs/vuln
Project root: /workspaces/id_2026
Scripts path: /workspaces/id_2026/scripts
Importing from: 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")
✓ 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}")
Repos path: /workspaces/id_2026/data/repos
Output path: /workspaces/id_2026/data/results
Dry run: False

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}")
Repositorios encontrados: 3
  - data/repos/claude-code-action
  - data/repos/genai-code-review
  - data/repos/opencode
print("Iniciando análisis CodeQL con scripts/generate_codeql.py...")
analizador.run()
print("✓ Análisis completado")
INFO | Usando CodeQL CLI: /usr/local/bin/codeql
INFO | === Diagnóstico del Entorno CodeQL ===
Iniciando análisis CodeQL con scripts/generate_codeql.py...
INFO | ✓ CodeQL CLI: CodeQL command-line toolchain release 2.25.1.
INFO | ✓ Node.js: v20.20.2
INFO | ✓ npm: 10.8.2
INFO | Verificando query packs...
INFO | ✓ Query pack codeql/python-queries disponible
INFO | ✓ Query pack codeql/javascript-queries disponible
INFO | ✓ Query pack codeql/java-queries disponible
INFO | === Fin Diagnóstico ===

INFO | [1/3] Procesando repositorio data/repos/claude-code-action
INFO | Lenguaje detectado en claude-code-action: javascript
INFO | Directorio temporal CodeQL: /tmp/codeql_analysis
INFO | Creando base de datos CodeQL para claude-code-action (lenguaje: javascript)...
INFO | Usando suite compilada: /home/vscode/.codeql/packages/codeql/javascript-queries/2.3.6/codeql-suites/javascript-security-and-quality.qls
INFO | SARIF guardado en: /workspaces/id_2026/data/results/claude-code-action_temp.sarif (284762 bytes)
INFO | SARIF output length: 284762 bytes
INFO | parse_sarif: recibiendo 284762 bytes
INFO | parse_sarif: 1 runs encontrados
INFO | parse_sarif: 16 resultados en run[0]
INFO | parse_sarif: total_issues=16
INFO | save_analysis: claude-code-action con 16 issues
INFO | Analisis CodeQL guardado en data/results/claude-code-action-codeql.json
INFO | [2/3] Procesando repositorio data/repos/genai-code-review
INFO | Lenguaje detectado en genai-code-review: python
INFO | Creando base de datos CodeQL para genai-code-review (lenguaje: python)...
INFO | Usando suite compilada: /home/vscode/.codeql/packages/codeql/python-queries/1.7.11/codeql-suites/python-security-and-quality.qls
INFO | SARIF guardado en: /workspaces/id_2026/data/results/genai-code-review_temp.sarif (132815 bytes)
INFO | SARIF output length: 132815 bytes
INFO | parse_sarif: recibiendo 132815 bytes
INFO | parse_sarif: 1 runs encontrados
INFO | parse_sarif: 4 resultados en run[0]
INFO | parse_sarif: total_issues=4
INFO | save_analysis: genai-code-review con 4 issues
INFO | Analisis CodeQL guardado en data/results/genai-code-review-codeql.json
INFO | [3/3] Procesando repositorio data/repos/opencode
INFO | Lenguaje detectado en opencode: javascript
INFO | Creando base de datos CodeQL para opencode (lenguaje: javascript)...
INFO | Usando suite compilada: /home/vscode/.codeql/packages/codeql/javascript-queries/2.3.6/codeql-suites/javascript-security-and-quality.qls
INFO | SARIF guardado en: /workspaces/id_2026/data/results/opencode_temp.sarif (1454012 bytes)
INFO | SARIF output length: 1454012 bytes
INFO | parse_sarif: recibiendo 1454012 bytes
INFO | parse_sarif: 1 runs encontrados
INFO | parse_sarif: 218 resultados en run[0]
INFO | parse_sarif: total_issues=218
INFO | save_analysis: opencode con 218 issues
INFO | Analisis CodeQL guardado en data/results/opencode-codeql.json
INFO | Resumen final | total_repos=3 | repos_analizados=3 | archivos_generados=3 | omitidos=0 | errores=0
✓ 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)")
📁 Buscando en: /workspaces/id_2026/data/results
✓ Archivos de análisis CodeQL encontrados: 3
  - claude-code-action-codeql.json (6.9 KB)
  - genai-code-review-codeql.json (1.8 KB)
  - opencode-codeql.json (84.2 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")
📂 Buscando en: /workspaces/id_2026/data/results
📋 Archivos encontrados: 3
📖 Leyendo: claude-code-action-codeql.json

📊 ANÁLISIS: claude-code-action
==================================================
✓ Total de problemas encontrados: 16

🔍 Problemas por severidad:
  🟡 warning: 16
# 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")
📋 Leyendo: claude-code-action-codeql.json
📊 Total de issues en archivo: 16

📋 Primeros 20 problemas encontrados:
====================================================================================================

1. [WARNING] js/incomplete-multi-character-sanitization
   📝 This string may still contain [<!--](1), which may cause an HTML element injecti
   📂 src/github/utils/sanitizer.ts

2. [WARNING] js/xss-through-dom
   📝 [DOM text](1) is reinterpreted as HTML without escaping meta-characters.
   📂 docs/create-app.html

3. [WARNING] js/http-to-file-access
   📝 Write to file system depends on [Untrusted data](1).
   📂 src/github/utils/image-downloader.ts

4. [WARNING] js/file-access-to-http
   📝 Outbound network request depends on [file data](1).
   📂 src/entrypoints/post-buffered-inline-comments.ts

5. [WARNING] js/file-access-to-http
   📝 Outbound network request depends on [file data](1).
   📂 src/mcp/github-file-ops-server.ts

6. [WARNING] js/regex/missing-regexp-anchor
   📝 When this is used as a regular expression on a URL, it may match anywhere, and a
   📂 src/github/api/config.ts

7. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
Insecure creation of file in 
   📂 base-action/src/prepare-prompt.ts

8. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 base-action/test/prepare-prompt.test.ts

9. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 base-action/test/prepare-prompt.test.ts

10. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 base-action/test/prepare-prompt.test.ts

11. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 base-action/test/setup-claude-code-settings.test.ts

12. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 src/github/utils/image-downloader.ts

13. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 test/github-file-ops-path-validation.test.ts

14. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 test/github-file-ops-path-validation.test.ts

15. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 test/github-file-ops-path-validation.test.ts

16. [WARNING] js/insecure-temporary-file
   📝 Insecure creation of file in [the os temp dir](1).
   📂 test/github-file-ops-path-validation.test.ts

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}")

📊 ESTADÍSTICAS CONSOLIDADAS DE CODEQL
============================================================

📁 Repositorios analizados: 3
🔴 Total de problemas: 238

📋 Problemas por repositorio:
  • claude-code-action: 16
  • genai-code-review: 4
  • opencode: 218

⚠️  Problemas por severidad:
  🔴 error: 0
  ⚪ note: 0
  🟡 warning: 238

🎯 Top 10 reglas más comunes:
  • js/unused-local-variable: 112
  • js/trivial-conditional: 33
  • js/property-access-on-non-object: 20
  • js/insecure-temporary-file: 14
  • js/http-to-file-access: 6
  • js/incomplete-sanitization: 5
  • js/syntax-error: 4
  • js/useless-expression: 4
  • js/useless-assignment-to-local: 4
  • py/unused-import: 3

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:

  1. Revisar vulnerabilidades críticas: Filtrar por level: "error" para identificar problemas de seguridad críticos
  2. Seguimiento: Integrar con sistemas de seguimiento de mermas (issues, PRs) para gestionar remediación
  3. Análisis de tendencias: Comparar resultados entre repositorios y períodos de tiempo
  4. 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:
{
    "repositories": [
        {
            "url": "https://github.com/owner/repo-name.git",
            "path": "data/repos/repo-name",
            "ref": "v1.0.0"
        }
    ]
}
  1. Ejecuta en la terminal (esto clonará los repositorios y los agregará como submódulos):
uv run scripts/add_submodules.py
  1. Ejecuta el análisis nuevamente desde el notebook o la terminal:
uv run scripts/generate_codeql.py