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¶
3.2 Verificacion de Instalacion¶
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¶
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:
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:
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¶
- Precision alta: Muy pocos falsos positivos
- Analisis profundo: Sigue flujo entre funciones/archivos
- Queries probados: Mantenidos por GitHub Security Lab
- Estandar SARIF: Facil integracion
- Extensible: Permite queries custom
11.2 Desventajas¶
- Lento: Puede tardar minutos
- Pesado: Requiere crear base de datos completa
- Dependencia externa: Requiere CodeQL CLI instalado
- Lenguajes limitados: No todos los lenguajes soportados
- 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:
Solucion:
12.2 Creacion de DB Falla¶
Error:
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:
Solucion:
12.4 Queries no Encontrados¶
Error:
Solucion:
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¶
13.3 Proceso Interno¶
- mcp-scan detecta (Pattern + Taint):
MCP-A001: Tool input flows to command execution-
Confidence: High
-
CodeQL analiza:
-
CodeQL encuentra:
py/command-line-injection-
Level: error
-
Fusion:
- Hallazgo de mcp-scan marcado como
CodeQLConfirmed: true - 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