🔐 Cómo configurar CodeQL para ejecutar solo las queries que necesitas (OWASP Top 10 + custom queries)

¡Hola developer 👋🏻!

Si estás evaluando GitHub Advanced Security es posible que cuando revises la parte de Code scanning, que analiza las vulnerabilidades que podemos introducir, sin querer claro está 😅, en nuestro propio código, puede que te preguntes:

👉 “¿Puedo controlar qué analiza CodeQL?”
👉 “¿Puedo limitarlo al OWASP Top 10 e incluso incluir mis propias reglas?”

Y la respuesta es: y en este artículo te cuento cómo configurarlo.

Si no conoces muchos de GitHub Advanced Security aquí te dejo un vídeo de mi canal donde hago un repaso de sus funcionalidades principals:

🤔 ¿Por qué limitar las queries de CodeQL?

Por defecto, CodeQL ejecuta cientos de queries de seguridad. Aquí puedes encontrar las CWEs que cubre a día de hoy. Esto está genial… pero en algunos casos algunos el sentimiento es:

❌ Ejecuciones muy largas en repositorios inmensos
❌ Genera ruido
❌ Puede ser difícil priorizar
❌ No siempre encaja con tus políticas de seguridad

Y aquí es donde entra el verdadero valor: 👉 definir tu propio conjunto de queries siguiendo marcos normativos o incluso añadiendo alguna query interna propia que también se quiere tener en consideración.

📦 Repositorio de ejemplo

Así para poder ilustrar esto he creado un repositorio que te dejo en mi cuenta de GitHub, en el cual me he marcado dos objetivos:

1. Ejecutar solo un subconjunto de queries

En lugar de usar:

queries: security-and-quality

Puedes definir exactamente qué quieres ejecutar:

  • OWASP Top 10
  • solo ciertos CWE
  • queries críticas
  • etc.

OWASP Top 10 es el estándar más común en AppSec.

Cada categoría (por ejemplo Injection) se corresponde con múltiples CWE y queries.

👉 Ejemplo:

  • SQL Injection → CWE-89
  • XSS → CWE-79

Y CodeQL ya tiene queries que cubren estos casos.

Incluso puedes crear suites específicas tipo:

name: OWASP-Top-10
queries:
  - codeql/java/queries/security/sql-injection.ql
  - codeql/javascript/queries/security/xss.ql

Este enfoque está bastante extendido en la comunidad (Medium)

2. Añadir custom queries

Aquí es donde la cosa se pone interesante 🔥 CodeQL permite escribir queries propias porque:

👉 trata el código como datos consultables
👉 puedes detectar patrones específicos de tu empresa

Ejemplos reales:

  • uso de librerías internas inseguras
  • validaciones corporativas obligatorias
  • patrones legacy peligrosos

Vale, pues ahora ¿Cómo configuramos esto? Pues vamos a verlo 😃

Configuración del flujo avanzado de GitHub Actions con queries personalizadas para CodeQL

Para que todo esto sea posible tienes que tener configurado CodeQL en su modo avanzado. Esto lo que significa es que necesitamos tener el flujo definido en el repo o al menos tener un flujo reusable en algún sitio donde tener toda esta configuración. Pero antes de nada necesitamos un archivo de configuración con las reglas personalizadas.

1. Crear tu archivo de configuración

En .github/codeql/codeql-config.yml:

Lo que tengo es algo como lo siguiente:

# Configuración de CodeQL para OWASP Top 10
# Este archivo indica a CodeQL que use SOLO la query suite personalizada
# de OWASP Top 10, en lugar de las suites predefinidas (default o security-extended).
#
# Documentación:
# https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning
name: "CodeQL - OWASP Top 10"
# ⚠️ CLAVE: Desactivar las queries predefinidas (default suite)
# Sin esto, CodeQL ejecuta las queries por defecto ADEMÁS de las personalizadas
disable-default-queries: true
# Queries a ejecutar:
# 1. Suite OWASP Top 10 (2025) - queries estándar filtradas por marco normativo
# 2. Queries personalizadas de negocio bancario (custom rules)
queries:
  - uses: ./.github/codeql/owasp-top-10-js.qls
  - uses: ./.github/codeql/custom-queries
# Paths a analizar (solo el código fuente de la aplicación)
paths:
  - src
# Paths a ignorar (tests, fixtures, etc.)
paths-ignore:
  - node_modules
  - "**/*.test.js"
  - "**/*.spec.js"

Este me va a permitir indicar dónde están las queries que quiero lanzar. En el caso del archivo .github/codeql/owasp-top-10-js.qls lo que he hecho es algo como lo siguiente:

# -----------------------------------------------------------
# CodeQL Query Suite: OWASP Top 10 (2025) - JavaScript/TypeScript
# -----------------------------------------------------------
#
# Este archivo define un conjunto personalizado de queries de CodeQL
# que cubren exclusivamente las categorías del OWASP Top 10 (2025).
#
# Al usar esta suite en lugar de "security-extended" o "default",
# solo se ejecutarán las queries relevantes para OWASP Top 10,
# filtrando cualquier otra regla que no aplique a este marco normativo.
#
# Documentación de query suites:
# https://codeql.github.com/docs/codeql-cli/creating-codeql-query-suites/
#
# Lista completa de CWEs cubiertos por CodeQL:
# https://codeql.github.com/codeql-query-help/full-cwe/
#
# -----------------------------------------------------------
# Para limitar aún más por severidad o precisión, descomenta:
#- include:
#    problem.severity:
#    - medium
#    - high
#    - very-high
#    - critical
#    precision:
#    - high
#    - very-high
# -----------------------------------------------------------
# Importar el pack de queries de JavaScript/TypeScript
# Necesario cuando se usa disable-default-queries: true en la config
- qlpack: codeql/javascript-queries
- include:
    id:
    # =========================================================
    # A01:2025 - Broken Access Control
    # Mantiene #1. Ahora incluye SSRF (antes era A10:2021)
    # CWE-22: Path Traversal
    # CWE-601: URL Redirection to Untrusted Site
    # CWE-918: SSRF (movido desde A10:2021)
    # =========================================================
    - js/path-injection
    - js/ml-powered/path-injection
    - js/server-side-unvalidated-url-redirection
    - js/client-side-unvalidated-url-redirection
    - js/user-controlled-bypass
    - js/exposure-of-private-files
    - js/request-forgery
    - js/client-side-request-forgery
    - js/file-access-to-http
    - js/http-to-file-access
    - js/sensitive-get-query
    # =========================================================
    # A02:2025 - Security Misconfiguration
    # Sube de A05:2021 a A02:2025
    # CWE-16: Configuration
    # CWE-611: XXE
    # =========================================================
    - js/cors-misconfiguration-for-credentials
    - js/disabling-certificate-validation
    - js/disabling-electron-websecurity
    - js/enabling-electron-insecure-content
    - js/functionality-from-untrusted-source
    - js/unsafe-external-link
    - js/double-escaping
    - js/incomplete-hostname-regexp
    - js/incomplete-url-scheme-check
    - js/incomplete-url-substring-sanitization
    - js/incorrect-suffix-check
    - js/incomplete-sanitization
    - js/incomplete-html-attribute-sanitization
    - js/incomplete-multi-character-sanitization
    - js/samesite-none-cookie
    # =========================================================
    # A03:2025 - Software Supply Chain Failures
    # Expandido desde A06:2021 (Vulnerable and Outdated Components)
    # (Dependabot + CodeQL para dependencias inseguras)
    # =========================================================
    - js/insecure-dependency
    - js/insecure-download
    # =========================================================
    # A04:2025 - Cryptographic Failures
    # Baja de A02:2021 a A04:2025
    # CWE-327: Use of Broken/Weak Crypto Algorithm
    # CWE-328: Reversible One-Way Hash
    # CWE-330: Insufficient Randomness
    # =========================================================
    - js/weak-cryptographic-algorithm
    - js/insufficient-key-size
    - js/biased-cryptographic-random
    - js/insecure-randomness
    - js/clear-text-storage-of-sensitive-data
    - js/clear-text-logging
    - js/clear-text-cookie
    # =========================================================
    # A05:2025 - Injection
    # Baja de A03:2021 a A05:2025
    # CWE-79: XSS
    # CWE-78: OS Command Injection
    # CWE-89: SQL Injection
    # CWE-94: Code Injection
    # CWE-917: Expression Language Injection
    # CWE-1321: Prototype Pollution
    # =========================================================
    - js/sql-injection
    - js/ml-powered/sql-injection
    - js/nosql-injection
    - js/ml-powered/nosql-injection
    - js/reflected-xss
    - js/stored-xss
    - js/xss
    - js/xss-through-dom
    - js/xss-through-exception
    - js/ml-powered/xss
    - js/command-line-injection
    - js/indirect-command-line-injection
    - js/shell-command-constructed-from-input
    - js/shell-command-injection-from-environment
    - js/code-injection
    - js/unsafe-code-construction
    - js/bad-code-sanitization
    - js/bad-tag-filter
    - js/html-constructed-from-input
    - js/unsafe-html-expansion
    - js/prototype-pollution
    - js/prototype-polluting-assignment
    - js/prototype-pollution-utility
    - js/tainted-format-string
    - js/template-object-injection
    - js/xpath-injection
    - js/xml-bomb
    - js/xxe
    - js/log-injection
    - js/regex/missing-regexp-anchor
    # =========================================================
    # A06:2025 - Insecure Design
    # Baja de A04:2021 a A06:2025
    # CWE-799: Improper Control of Interaction Frequency
    # =========================================================
    - js/missing-rate-limiting
    # =========================================================
    # A07:2025 - Authentication Failures
    # Mantiene #7 (renombrado de "Identification and Auth Failures")
    # CWE-798: Hardcoded Credentials
    # CWE-259: Hardcoded Password
    # CWE-384: Session Fixation
    # =========================================================
    - js/hardcoded-credentials
    - js/empty-password-in-configuration-file
    - js/password-in-configuration-file
    - js/jwt-missing-verification
    - js/missing-token-validation
    - js/missing-origin-check
    - js/session-fixation
    - js/insufficient-password-hash
    - js/client-exposed-cookie
    # =========================================================
    # A08:2025 - Software or Data Integrity Failures
    # Mantiene #8
    # CWE-502: Deserialization of Untrusted Data
    # =========================================================
    - js/unsafe-deserialization
    - js/unsafe-jquery-plugin
    - js/zipslip
    - js/identity-replacement
    # =========================================================
    # A09:2025 - Security Logging & Alerting Failures
    # Mantiene #9 (renombrado, ahora enfatiza "Alerting")
    # CWE-778: Insufficient Logging
    # (CodeQL tiene cobertura limitada aquí)
    # =========================================================
    - js/stack-trace-exposure
    - js/cross-window-information-leak
    # =========================================================
    # A10:2025 - Mishandling of Exceptional Conditions (NUEVO)
    # Reemplaza SSRF (que se movió a A01)
    # CWE-248: Uncaught Exception
    # CWE-754: Improper Check for Unusual/Exceptional Conditions
    # CWE-391: Unchecked Error Condition
    # =========================================================
    - js/resource-exhaustion
    # =========================================================
    # GitHub Actions (bonus - seguridad en CI/CD)
    # =========================================================
    - js/actions/command-injection
    - js/actions/pull-request-target
    - js/build-artifact-leak
    - js/host-header-forgery-in-email-generation
    - js/remote-property-injection
    - js/unsafe-dynamic-method-access
    - js/useless-regexp-character-escape

Aquí no te voy a engañar, le he pedido ayuda a GitHub Copilot para recopilar todas las reglas que me hacen falta y sería cuestión de probarlo bien para que no se nos olvide nada.

El segundo archivo apunta a una custom query en CodeQL que valida que haya una validación previa a ciertas operaciones que pueden ser críticas, por ejemplo en un banco. En la configuración a lo que se está apuntando en realidad es a una carpeta dentro de la cual hay dos archivos, el qlpack.yml:

# CodeQL Query Pack personalizado para reglas de negocio bancarias
# Este pack contiene queries custom que no están en las suites estándar de CodeQL
name: custom-banking-queries
version: 0.0.1
dependencies:
  codeql/javascript-all: "*"

Y por otro lado uno que he llamado missing-context-validation.ql:

/**
 * @name Operación bancaria sensible sin validación de contexto
 * @description Detecta llamadas a operaciones bancarias sensibles que no están
 *              precedidas por una llamada de validación de contexto en la misma función.
 *              En entornos bancarios, toda operación crítica debe validar el contexto
 *              (sesión, autorización) antes de ejecutarse.
 * @kind problem
 * @problem.severity error
 * @precision high
 * @id custom/missing-context-validation
 * @tags security
 *       banking
 *       compliance
 *       custom
 */
import javascript
/**
 * Nombres de operaciones bancarias sensibles.
 * Cualquier llamada a estas funciones DEBE estar precedida
 * por una validación de contexto en la misma función.
 */
predicate isSensitiveOperationName(string name) {
  name = "executeTransaction" or
  name = "transferFunds" or
  name = "processPayment" or
  name = "approveCredit" or
  name = "withdrawFunds"
}
/**
 * Nombres de funciones de validación de contexto.
 * Al menos una de estas debe aparecer antes de cualquier operación sensible.
 */
predicate isValidationFunctionName(string name) {
  name = "validateContext" or
  name = "validateSession" or
  name = "verifyAuthorization"
}
/**
 * Obtiene el nombre de la función llamada, sea como llamada directa
 * (executeTransaction(...)) o como método (bankService.executeTransaction(...))
 */
string getCallName(CallExpr call) {
  result = call.getCalleeName()
  or
  exists(DotExpr dot |
    dot = call.getCallee() and
    result = dot.getPropertyName()
  )
}
from CallExpr sensitiveCall, Function enclosingFunc, string opName
where
  // Identificar llamadas a operaciones sensibles
  opName = getCallName(sensitiveCall) and
  isSensitiveOperationName(opName) and
  // Obtener la función que contiene la llamada
  enclosingFunc = sensitiveCall.getEnclosingFunction() and
  // Verificar que NO existe una llamada de validación ANTES en la misma función
  not exists(CallExpr validationCall, string valName |
    valName = getCallName(validationCall) and
    isValidationFunctionName(valName) and
    validationCall.getEnclosingFunction() = enclosingFunc and
    validationCall.getLocation().getStartLine() < sensitiveCall.getLocation().getStartLine()
  )
select sensitiveCall,
  "La operación bancaria sensible '" + opName +
  "' se ejecuta sin validación de contexto previa. " +
  "Añade una llamada a validateContext(), validateSession() o verifyAuthorization() antes de esta operación."

Con todo esto ya solo nos queda usarlo como parte de nuestro flujo de GitHub Actions para esa configuración avanzada de CodeQL.

.github/workflows/codeql.yml

💡 Aquí es donde conectas todo:

name: "🛡️ CodeQL - OWASP Top 10"
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
  # Escaneo programado semanal (lunes a las 8:00 UTC)
  schedule:
    - cron: '0 8 * * 1'
jobs:
  analyze:
    name: Analyze (OWASP Top 10)
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
      actions: read
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v4
      with:
        languages: javascript-typescript
        # Usar la configuración personalizada que apunta a la suite OWASP
        config-file: ./.github/codeql/codeql-config.yml
    - name: Autobuild
      uses: github/codeql-action/autobuild@v4
    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v4
      with:
        category: "/language:javascript-typescript"

Aquí lo importante es que en el paso init le pasamos el config file que definí más arriba y con ello ya sabe cuáles son las reglas, preconstruidas y generadas por mi, que tiene que utilizar.

En el caso de las personalizadas, en este ejemplo, verás algo como lo siguiente:

y el detalle se vería así:

🧩 Estrategias que puedes aplicar

Aquí tienes varios patrones reales que puedes usar:

✅ 1. OWASP-only scanning

👉 Ideal para:

  • compliance
  • auditorías
  • equipos empezando con seguridad

⚡ 2. Only critical queries

👉 Reduce ruido
👉 Mejora adopción

🏢 3. Security policies corporativas

👉 Custom queries + suites
👉 Enforcing en PRs

🧪 4. Demo / training environments

👉 Como este repo 😄
👉 Muy útil para workshops o sesiones técnicas

Además, encaja perfectamente con el enfoque shift-left de GitHub Advanced Security, donde detectas vulnerabilidades antes de producción.

¡Nos vemos 👋🏻!

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.