Skip to content

Integracion con CodeQL

Documento tecnico para analistas de seguridad


1. Introduccion

mcp-scan integra CodeQL como motor de analisis secundario para confirmar hallazgos y detectar vulnerabilidades complejas que requieren analisis de flujo de datos avanzado. CodeQL es especialmente util para encontrar vulnerabilidades que requieren seguir el flujo a traves de multiples funciones y archivos.


2. Arquitectura de Integracion

2.1 Diagrama de Componentes

+------------------+
|    mcp-scan      |
|  Pattern/Taint   |  <-- Deteccion primaria
+------------------+
        |
        v
+------------------+
|  CodeQL Client   |  <-- internal/codeql/client.go
+------------------+
        |
        v
+------------------+
|   CodeQL CLI     |  <-- Binario externo
+------------------+
        |
        +---> database create (AST extraction)
        +---> database analyze (query execution)
        |
        v
+------------------+
|  SARIF Results   |
+------------------+
        |
        v
+------------------+
|  Finding Merger  |  <-- Combina con resultados de mcp-scan
+------------------+

2.2 Ubicacion del Codigo

Archivo principal: internal/codeql/client.go

type Client struct {
    binaryPath   string        // Ruta a binario codeql
    timeout      time.Duration // Timeout de analisis
    queriesDir   string        // Directorio de queries custom
    cacheEnabled bool          // Cachear bases de datos
}

3. Requisitos

3.1 Instalacion de CodeQL

macOS

# Via Homebrew
brew install codeql

# O descarga directa
wget https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql-osx64.zip
unzip codeql-osx64.zip
export PATH=$PATH:$(pwd)/codeql

Linux

wget https://github.com/github/codeql-cli-binaries/releases/latest/download/codeql-linux64.zip
unzip codeql-linux64.zip
export PATH=$PATH:$(pwd)/codeql

Windows

# Descargar de GitHub releases y agregar al PATH

3.2 Verificacion de Instalacion

codeql version

Debe mostrar version >= 2.14.0

3.3 Query Packs Necesarios

# Descargar packs estandar
codeql pack download codeql/python-queries
codeql pack download codeql/javascript-queries
codeql pack download codeql/go-queries

4. Cliente CodeQL

4.1 Configuracion

type Config struct {
    BinaryPath string        // Ruta a codeql (vacio = buscar en PATH)
    Timeout    time.Duration // Timeout de analisis (default: 30 min)
    QueriesDir string        // Queries custom
    Cache      bool          // Cachear DBs (default: true)
}

func DefaultConfig() Config {
    return Config{
        Timeout: 30 * time.Minute,
        Cache:   true,
    }
}

4.2 Inicializacion

func NewClient(cfg Config) (*Client, error) {
    binaryPath := cfg.BinaryPath
    if binaryPath == "" {
        // Buscar en PATH
        path, err := exec.LookPath("codeql")
        if err != nil {
            return nil, fmt.Errorf("codeql not found in PATH: %w", err)
        }
        binaryPath = path
    }

    // Verificar que funciona
    cmd := exec.Command(binaryPath, "version")
    if err := cmd.Run(); err != nil {
        return nil, fmt.Errorf("codeql version check failed: %w", err)
    }

    return &Client{
        binaryPath:   binaryPath,
        timeout:      cfg.Timeout,
        queriesDir:   cfg.QueriesDir,
        cacheEnabled: cfg.Cache,
    }, nil
}

4.3 Verificacion de Disponibilidad

func IsAvailable() bool {
    _, err := exec.LookPath("codeql")
    return err == nil
}

5. Comandos Ejecutados

5.1 Obtener Version

func (c *Client) Version(ctx context.Context) (string, error) {
    cmd := exec.CommandContext(ctx, c.binaryPath, "version", "--format=json")
    output, err := cmd.Output()
    if err != nil {
        return "", fmt.Errorf("codeql version failed: %w", err)
    }

    var result struct {
        Version string `json:"version"`
    }
    if err := json.Unmarshal(output, &result); err != nil {
        return strings.TrimSpace(string(output)), nil
    }
    return result.Version, nil
}

Comando ejecutado:

codeql version --format=json

5.2 Crear Base de Datos

func (c *Client) CreateDatabase(ctx context.Context, sourcePath, dbPath, language string) error {
    ctx, cancel := context.WithTimeout(ctx, c.timeout)
    defer cancel()

    args := []string{
        "database", "create",
        dbPath,
        "--language=" + language,
        "--source-root=" + sourcePath,
        "--overwrite",
    }

    cmd := exec.CommandContext(ctx, c.binaryPath, args...)
    cmd.Dir = sourcePath

    var stderr bytes.Buffer
    cmd.Stderr = &stderr

    if err := cmd.Run(); err != nil {
        return fmt.Errorf("database create failed: %w, stderr: %s", err, stderr.String())
    }
    return nil
}

Comando ejecutado:

codeql database create /tmp/mcp-scan-codeql-xxx/db \
    --language=python \
    --source-root=/path/to/code \
    --overwrite

Lenguajes soportados: | Lenguaje | Valor | |----------|-------| | Python | python | | JavaScript | javascript | | TypeScript | javascript (mismo extractor) | | Go | go |

5.3 Analizar Base de Datos

func (c *Client) AnalyzeDatabase(ctx context.Context, dbPath, outputPath string, queries ...string) error {
    ctx, cancel := context.WithTimeout(ctx, c.timeout)
    defer cancel()

    if len(queries) == 0 {
        queries = c.getDefaultQueries(dbPath)
    }

    args := []string{
        "database", "analyze",
        dbPath,
        "--format=sarifv2.1.0",
        "--output=" + outputPath,
        "--sarif-add-snippets",
        "--threads=0",  // Usar todos los cores
    }
    args = append(args, queries...)

    cmd := exec.CommandContext(ctx, c.binaryPath, args...)

    var stderr bytes.Buffer
    cmd.Stderr = &stderr

    if err := cmd.Run(); err != nil {
        return fmt.Errorf("database analyze failed: %w, stderr: %s", err, stderr.String())
    }
    return nil
}

Comando ejecutado:

codeql database analyze /tmp/mcp-scan-codeql-xxx/db \
    --format=sarifv2.1.0 \
    --output=/tmp/mcp-scan-codeql-xxx/results.sarif \
    --sarif-add-snippets \
    --threads=0 \
    codeql/python-queries:codeql-suites/python-security-extended.qls

5.4 Scan Completo (Combinado)

func (c *Client) ScanDirectory(ctx context.Context, sourcePath, language string, queries ...string) (*SARIFReport, error) {
    // Crear directorio temporal
    tmpDir, err := os.MkdirTemp("", "mcp-scan-codeql-*")
    if err != nil {
        return nil, fmt.Errorf("failed to create temp dir: %w", err)
    }
    if !c.cacheEnabled {
        defer os.RemoveAll(tmpDir)
    }

    dbPath := filepath.Join(tmpDir, "db")
    sarifPath := filepath.Join(tmpDir, "results.sarif")

    // 1. Crear base de datos
    if err := c.CreateDatabase(ctx, sourcePath, dbPath, language); err != nil {
        return nil, err
    }

    // 2. Analizar
    if err := c.AnalyzeDatabase(ctx, dbPath, sarifPath, queries...); err != nil {
        return nil, err
    }

    // 3. Parsear resultados
    return ParseSARIFFile(sarifPath)
}

5.5 Ejecutar Query Individual

func (c *Client) RunQuery(ctx context.Context, dbPath, queryPath string) (*SARIFReport, error) {
    ctx, cancel := context.WithTimeout(ctx, c.timeout)
    defer cancel()

    tmpDir, err := os.MkdirTemp("", "codeql-query-*")
    if err != nil {
        return nil, err
    }
    defer os.RemoveAll(tmpDir)

    sarifPath := filepath.Join(tmpDir, "results.sarif")

    args := []string{
        "database", "analyze",
        dbPath,
        queryPath,
        "--format=sarifv2.1.0",
        "--output=" + sarifPath,
        "--sarif-add-snippets",
    }

    cmd := exec.CommandContext(ctx, c.binaryPath, args...)

    var stderr bytes.Buffer
    cmd.Stderr = &stderr

    if err := cmd.Run(); err != nil {
        return nil, fmt.Errorf("query failed: %w, stderr: %s", err, stderr.String())
    }

    return ParseSARIFFile(sarifPath)
}

6. Query Suites

6.1 Suites por Defecto

func (c *Client) getDefaultQueries(dbPath string) []string {
    queries := []string{}

    // Agregar queries custom si existen
    if c.queriesDir != "" {
        queries = append(queries, c.queriesDir)
    }

    // Agregar suites estandar de seguridad
    queries = append(queries,
        "codeql/go-queries:codeql-suites/go-security-extended.qls",
        "codeql/python-queries:codeql-suites/python-security-extended.qls",
        "codeql/javascript-queries:codeql-suites/javascript-security-extended.qls",
    )

    return queries
}

6.2 Suites Disponibles

Lenguaje Suite Cobertura
Python python-security-extended.qls Seguridad completa
Python python-security-and-quality.qls Seguridad + calidad
JavaScript javascript-security-extended.qls Seguridad completa
JavaScript javascript-security-and-quality.qls Seguridad + calidad
Go go-security-extended.qls Seguridad completa
Go go-security-and-quality.qls Seguridad + calidad

6.3 Queries Relevantes para MCP

Query Lenguaje Detecta
py/command-line-injection Python RCE via subprocess
py/code-injection Python eval/exec con input
py/path-injection Python Path traversal
py/sql-injection Python SQLi
py/ssrf Python SSRF
py/clear-text-logging-sensitive-data Python Logging de secretos
js/command-line-injection JS/TS RCE via child_process
js/code-injection JS/TS eval con input
js/path-injection JS/TS Path traversal
js/sql-injection JS/TS SQLi
js/request-forgery JS/TS SSRF

7. Parseo de Resultados SARIF

7.1 Estructura SARIF

type SARIFReport struct {
    Schema  string `json:"$schema"`
    Version string `json:"version"`
    Runs    []Run  `json:"runs"`
}

type Run struct {
    Tool    Tool     `json:"tool"`
    Results []Result `json:"results"`
}

type Result struct {
    RuleID   string     `json:"ruleId"`
    Level    string     `json:"level"`
    Message  Message    `json:"message"`
    Locations []Location `json:"locations"`
}

7.2 Parseo de Archivo

func ParseSARIFFile(path string) (*SARIFReport, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("failed to read SARIF: %w", err)
    }

    var report SARIFReport
    if err := json.Unmarshal(data, &report); err != nil {
        return nil, fmt.Errorf("failed to parse SARIF: %w", err)
    }

    return &report, nil
}

8. Fusion de Hallazgos

8.1 Estrategia de Fusion

Los hallazgos de CodeQL se fusionan con los de mcp-scan:

func MergeFindings(mcpFindings []types.Finding, codeqlReport *codeql.SARIFReport) []types.Finding {
    merged := make([]types.Finding, 0, len(mcpFindings))

    // 1. Agregar hallazgos de mcp-scan
    for _, f := range mcpFindings {
        merged = append(merged, f)
    }

    // 2. Agregar hallazgos de CodeQL que no esten duplicados
    for _, run := range codeqlReport.Runs {
        for _, result := range run.Results {
            finding := convertCodeQLResult(result)

            // Verificar si ya existe
            if !isDuplicate(merged, finding) {
                // Marcar como confirmado por CodeQL
                finding.Evidence.CodeQLConfirmed = true
                merged = append(merged, finding)
            }
        }
    }

    // 3. Marcar hallazgos de mcp-scan confirmados por CodeQL
    for i, f := range merged {
        if hasCodeQLConfirmation(codeqlReport, f) {
            merged[i].Evidence.CodeQLConfirmed = true
            // Elevar confianza si estaba en medio
            if merged[i].Confidence == types.ConfidenceMedium {
                merged[i].Confidence = types.ConfidenceHigh
            }
        }
    }

    return merged
}

8.2 Conversion de Resultados CodeQL

func convertCodeQLResult(result codeql.Result) types.Finding {
    loc := result.Locations[0].PhysicalLocation

    // Mapear nivel de severidad
    severity := mapCodeQLLevel(result.Level)

    // Mapear rule ID a clase de vulnerabilidad
    class := mapCodeQLRule(result.RuleID)

    return types.Finding{
        RuleID:      "CODEQL-" + result.RuleID,
        Severity:    severity,
        Confidence:  types.ConfidenceHigh,  // CodeQL tiene alta precision
        Class:       class,
        Location: types.Location{
            File:      loc.ArtifactLocation.URI,
            StartLine: loc.Region.StartLine,
            EndLine:   loc.Region.EndLine,
        },
        Description: result.Message.Text,
        Evidence: types.Evidence{
            Snippet:         loc.Region.Snippet.Text,
            CodeQLConfirmed: true,
        },
    }
}

func mapCodeQLLevel(level string) types.Severity {
    switch level {
    case "error":
        return types.SeverityCritical
    case "warning":
        return types.SeverityHigh
    case "note":
        return types.SeverityMedium
    default:
        return types.SeverityInfo
    }
}

8.3 Deteccion de Duplicados

func isDuplicate(findings []types.Finding, candidate types.Finding) bool {
    for _, f := range findings {
        // Mismo archivo y linea similar
        if f.Location.File == candidate.Location.File &&
           abs(f.Location.StartLine - candidate.Location.StartLine) <= 2 {
            // Misma clase de vulnerabilidad
            if f.Class == candidate.Class {
                return true
            }
        }
    }
    return false
}

9. Configuracion

9.1 En .mcp-scan.yaml

codeql:
  enabled: true
  binary_path: ""  # Vacio = buscar en PATH
  timeout: "30m"
  cache: true
  queries_dir: "./custom-queries"  # Opcional

  # Suites adicionales
  extra_suites:
    - "my-custom-suite.qls"

9.2 Variables de Entorno

# Alternativa a config file
export MCP_SCAN_CODEQL_ENABLED=true
export MCP_SCAN_CODEQL_PATH=/path/to/codeql
export MCP_SCAN_CODEQL_TIMEOUT=30m

10. Lenguajes Soportados

func (c *Client) SupportedLanguages(ctx context.Context) ([]string, error) {
    cmd := exec.CommandContext(ctx, c.binaryPath, "resolve", "languages")
    output, err := cmd.Output()
    if err != nil {
        return nil, err
    }

    var languages []string
    for _, line := range strings.Split(string(output), "\n") {
        line = strings.TrimSpace(line)
        if line != "" && !strings.HasPrefix(line, "#") {
            parts := strings.Fields(line)
            if len(parts) > 0 {
                languages = append(languages, parts[0])
            }
        }
    }
    return languages, nil
}

Comando ejecutado:

codeql resolve languages

Salida tipica:

cpp (cpp-queries)
csharp (csharp-queries)
go (go-queries)
java (java-queries)
javascript (javascript-queries)
python (python-queries)
ruby (ruby-queries)
swift (swift-queries)


11. Ventajas y Desventajas

11.1 Ventajas de CodeQL

  1. Precision alta: Muy pocos falsos positivos
  2. Analisis profundo: Sigue flujo entre funciones/archivos
  3. Queries probados: Mantenidos por GitHub Security Lab
  4. Estandar SARIF: Facil integracion
  5. Extensible: Permite queries custom

11.2 Desventajas

  1. Lento: Puede tardar minutos
  2. Pesado: Requiere crear base de datos completa
  3. Dependencia externa: Requiere CodeQL CLI instalado
  4. Lenguajes limitados: No todos los lenguajes soportados
  5. No MCP-aware: No entiende superficie MCP especificamente

11.3 Cuando Usar CodeQL

Recomendado: - Analisis profundo para certificacion - Confirmacion de hallazgos criticos - Proyectos grandes con multiples archivos - Cuando se necesita alta confianza

No recomendado: - CI/CD rapido (usar modo fast sin CodeQL) - Proyectos pequenos de un archivo - Cuando Ollama no esta disponible


12. Troubleshooting

12.1 CodeQL no Encontrado

Error:

codeql not found in PATH

Solucion:

# Verificar instalacion
which codeql

# Agregar al PATH
export PATH=$PATH:/path/to/codeql

12.2 Creacion de DB Falla

Error:

database create failed: No source files found

Posibles causas: 1. Directorio vacio 2. Lenguaje incorrecto 3. Archivos excluidos por .gitignore

Solucion:

# Verificar archivos
find /path/to/code -name "*.py" | wc -l

# Especificar lenguaje correcto
codeql database create db --language=python --source-root=/path/to/code

12.3 Analisis Timeout

Error:

context deadline exceeded

Solucion:

# Aumentar timeout en config
codeql:
  timeout: "60m"

12.4 Queries no Encontrados

Error:

Could not find pack codeql/python-queries

Solucion:

# Descargar packs
codeql pack download codeql/python-queries


13. Ejemplo de Flujo Completo

13.1 Codigo a Analizar

# server.py
import subprocess
from mcp import Server

server = Server()

@server.tool()
def run_command(cmd: str):
    """Execute a shell command."""
    result = subprocess.run(cmd, shell=True, capture_output=True)
    return result.stdout.decode()

13.2 Ejecucion

# Con CodeQL habilitado
mcp-scan scan ./server.py --codeql

13.3 Proceso Interno

  1. mcp-scan detecta (Pattern + Taint):
  2. MCP-A001: Tool input flows to command execution
  3. Confidence: High

  4. CodeQL analiza:

    codeql database create /tmp/xxx/db --language=python --source-root=.
    codeql database analyze /tmp/xxx/db --format=sarifv2.1.0 --output=results.sarif python-security-extended.qls
    

  5. CodeQL encuentra:

  6. py/command-line-injection
  7. Level: error

  8. Fusion:

  9. Hallazgo de mcp-scan marcado como CodeQLConfirmed: true
  10. Confianza elevada si era Medium

13.4 Output Final

{
  "findings": [
    {
      "rule_id": "MCP-A001",
      "severity": "critical",
      "confidence": "high",
      "location": {
        "file": "server.py",
        "line": 10
      },
      "description": "Tool input flows to command execution",
      "evidence": {
        "snippet": "subprocess.run(cmd, shell=True, ...)",
        "codeql_confirmed": true
      }
    }
  ],
  "codeql_version": "2.15.1",
  "codeql_queries_run": ["python-security-extended.qls"]
}

Siguiente documento: clases-vulnerabilidad.md