Security
9 min read
1109 views

Mezcla de Contexto: Explotando @ExecutionContext en Módulos GraphQL

IT
InstaTunnel Team
Published by our engineering team
Mezcla de Contexto: Explotando @ExecutionContext en Módulos GraphQL

GraphQL ha madurado hasta convertirse en la columna vertebral de capas de datos federadas y modularizadas en la arquitectura web moderna. Pero a medida que las capas de abstracción se vuelven más sofisticadas, también lo hacen las superficies de ataque ocultas debajo de ellas. Uno de estos riesgos se encuentra en la intersección de Dependency Injection, JavaScript asíncrono y estado singleton compartido — y es más peligroso de lo que la mayoría de los equipos cree.

Este artículo desglosa una clase crítica de vulnerabilidad en el framework de Módulos GraphQL, explica la mecánica en el mundo real, la vincula a un CVE de Node.js recientemente parcheado que demuestra que la amenaza no es teórica, y te ofrece pasos concretos para proteger tu API.


¿Qué son los Módulos GraphQL?

GraphQL Modules es un conjunto de herramientas mantenido por The Guild que permite a los equipos construir módulos reutilizables, mantenibles y testables para servidores GraphQL. En su núcleo, cuenta con un potente sistema de Dependency Injection que permite a los desarrolladores definir “Proveedores” — servicios responsables de la lógica de negocio, recuperación de datos y autorización.

Una de sus funciones más útiles es el decorador @ExecutionContext.


El papel de @ExecutionContext

En una ejecución estándar de GraphQL, un objeto context se pasa a cada resolver. Normalmente lleva la sesión del usuario actual, tokens de autenticación y conexiones a bases de datos.

Lo complicado: cuando trabajas con Proveedores Singleton — servicios instanciados una sola vez durante toda la vida de la aplicación — no puedes acceder directamente al contexto por solicitud. El decorador @ExecutionContext fue diseñado para cerrar esa brecha. Permite que un servicio Singleton acceda dinámicamente al contexto de la ejecución actual, generalmente aprovechando el seguimiento asíncrono de recursos de Node.js.

Como describen los documentos oficiales de GraphQL Modules:

e “Gracias al decorador @ExecutionContext, cada proveedor Singleton obtiene acceso al Contexto de GraphQL y al inyector con alcance a la Operación.”

Suena bien. Pero en la práctica, bajo carga concurrente, es una pistola cargada.


La Vulnerabilidad: Mezcla de Contexto Bajo Carga Concurrente

La vulnerabilidad es una condición de carrera (CWE-362) que surge cuando múltiples solicitudes paralelas alcanzan un servicio Singleton usando @ExecutionContext.

Causa raíz: Contaminación de estado en el contenedor DI

El problema proviene de una aislamiento inadecuado del contexto de ejecución dentro del contenedor DI. Cuando dos o más solicitudes se procesan simultáneamente, el framework puede fallar en sincronizar correctamente la inyección del contexto en la instancia compartida del servicio.

Bajo carga pesada, considera este escenario:

  • Usuario A (Administrador) y Usuario B (usuario normal) envían solicitudes casi al mismo milisegundo.
  • Ambas solicitudes invocan un servicio Singleton que usa @ExecutionContext.
  • El framework asigna el contexto del Usuario A al Singleton.
  • El resolver del Usuario A llega a una llamada await — una consulta a la base de datos, una petición API, cualquier operación asíncrona.
  • Mientras Usuario A está suspendido en el await, el Event Loop de Node.js procesa la solicitud del Usuario B.
  • Debido a que la instancia Singleton es compartida, su referencia interna context ahora se sobrescribe — o peor, el contexto elevado de Usuario A se filtra en la ejecución de Usuario B.

Este es un clásico error de Time-of-Check Time-of-Use (TOCTOU). La instancia lee el contexto en un momento dado, pero para cuando actúa en ese contexto para una operación sensible, el valor ha sido cambiado por otra solicitud concurrente.

Por qué Node.js hace esto peor

Node.js es de un solo hilo, pero maneja la concurrencia a través del Event Loop y callbacks asíncronos. El módulo async_hooks (y su sucesor moderno AsyncLocalStorage) están diseñados para rastrear el contexto de ejecución a través de límites asíncronos. Sin embargo, cualquier await crea un “punto de suspensión” — una ventana donde el event loop puede procesar otra solicitud y sobrescribir referencias compartidas en un Singleton.

Los issues en GitHub de GraphQL Modules han documentado casos reales en producción donde inyectar accidentalmente un servicio con alcance a la sesión en un proveedor con alcance a la aplicación (Singleton) causó fugas de memoria y contaminación de estado. La mezcla de contexto es la forma de impacto en seguridad de ese mismo error arquitectónico.


Caso real: CVE-2025-59466

Esto no es solo una clase teórica de bug. El 13 de enero de 2026, el equipo de seguridad de Node.js parcheó CVE-2025-59466 — una falla crítica en async_hooks que había estado en el código durante nueve años.

La vulnerabilidad mostró que cuando ocurre un desbordamiento de pila dentro de un callback de async_hooks (provocado por una entrada recursiva profundamente anidada), el runtime invoca un manejador de errores fatales y termina todo el proceso del servidor — sin oportunidad de que el código del usuario capture el error. Cada solicitud en curso se descarta.

Más relevante para la mezcla de contexto: este CVE reveló que el seguimiento de contexto basado en async_hooks es frágil bajo condiciones adversas. Cualquier cosa que interrumpa la cadena de ejecución asíncrona normal — anidamiento extremo, resolución recursiva o alta concurrencia — puede corromper la propagación del contexto en frameworks como GraphQL Modules.

Versiones afectadas de Node.js: 8.x a 18.x (todas con fin de soporte y sin parches). Versiones parcheadas: 20.20.0, 22.22.0, 24.13.0, y 25.3.0.

Además, el equipo de Node.js ha marcado oficialmente la API de async_hooks como Estabilidad 1 - Experimental, recomendando encarecidamente migrar a AsyncLocalStorage. En Node.js 24+, AsyncLocalStorage fue reimplementado sobre el AsyncContextFrame de V8, eliminando su dependencia en async_hooks.createHook() y eliminando toda esta fragilidad.

La vulnerabilidad de mezcla de contexto en Módulos GraphQL vive en esta misma área: una abstracción a nivel de framework que confía en que el aislamiento de contexto basado en async_hooks se mantenga bajo carga concurrente.


Escenario de explotación: Escalada de privilegios en una aplicación financiera

Imagina una aplicación financiera donde un administrador procesa en lote la nómina mientras un usuario malicioso simplemente actualiza su perfil.

La configuración

La aplicación usa un FinanceService para autorizar transacciones. Por rendimiento, se declara como Singleton y usa @ExecutionContext para obtener el token del llamante:

import { Injectable, ExecutionContext, Scope } from 'graphql-modules';

@Injectable({ scope: Scope.Singleton })
class FinanceService {
  @ExecutionContext()
  private context: ExecutionContext;

  async transferFunds(amount: number) {
    // Este es el punto de fallo bajo carga concurrente
    const token = this.context.injector.get(AUTH_TOKEN);

    return await bankApi.post('/transfer', { amount }, {
      headers: { Authorization: `Bearer ${token}` }
    });
  }
}

El vector de ataque

El atacante no necesita habilidades avanzadas. Un simple script — o incluso Burp Suite’s Intruder — enviando una avalancha de solicitudes durante una sesión de administrador conocida, es suficiente. El objetivo es solo el timing.

La colisión

  1. El Usuario A (Admin) activa una mutación sensible transferFunds.
  2. El framework establece @ExecutionContext con el contexto del Admin en el Singleton.
  3. El resolver del Usuario A llega a la llamada await bankApi.post(...) y se suspende.
  4. El Event Loop procesa la solicitud del Usuario B.
  5. Debido a la condición de carrera en la referencia del contexto del Singleton, la ejecución del Usuario B sobrescribe o hereda el token de Admin del Usuario A.
  6. La solicitud del Usuario B ahora ejecuta transferFunds con el encabezado Authorization del Admin.
  7. La RBAC se omite por completo. Los logs de auditoría muestran la transacción como si viniera del Admin.

Patrón de prueba de concepto

Los investigadores de seguridad validan esta clase de bug usando inyección de promesas diferidas — pausando intencionalmente la ejecución de una solicitud en un límite await, y luego compitiendo con otra solicitud para demostrar que la referencia del contexto en el Singleton cambia. Bajo un entorno de pruebas con temporización, el cambio de contexto se reproduce de manera confiable en alta concurrencia.


Evaluación del impacto

La gravedad de esta clase de vulnerabilidad es ALTA. El impacto abarca tanto la Confidencialidad como la Integridad.

Área de riesgo Impacto potencial
Acceso no autorizado a datos Los usuarios pueden ver PII, registros financieros o mensajes privados de otros usuarios
Escalada de privilegios Un usuario con bajo privilegio puede realizar acciones administrativas en nombre de otro usuario
Toma de control de cuenta Los tokens de sesión filtrados pueden mantener el acceso mucho después de cerrar la ventana de carrera
Corrupción del registro de auditoría Mutaciones maliciosas aparecen en los logs como si las hubiera realizado el víctima

Remedios y mejores prácticas

1. Actualizar GraphQL Modules

Si usas una versión vulnerable, aplica el parche inmediatamente.

npm install graphql-modules@latest
# o
yarn add graphql-modules@latest

La solución implica rearquitectar cómo @ExecutionContext rastrea el inyector interno, asegurando que las referencias de contexto nunca se compartan entre límites asíncronos.

Versiones corregidas: - graphql-modules ≥ 2.4.1 - graphql-modules ≥ 3.1.1 - @envelop/graphql-modules ≥ 9.1.0

2. Actualizar Node.js

Actualizar a una de las versiones parcheadas: 20.20.0, 22.22.0, 24.13.0, o 25.3.0. Estas versiones abordan la fragilidad subyacente de async_hooks. Si puedes migrar a Node.js 24+, AsyncLocalStorage ya no depende de async_hooks.createHook().

3. Preferir alcance a nivel de operación sobre Singleton para servicios sensibles a la autenticación

Esta es la mitigación más sólida estructuralmente. La documentación de GraphQL Modules es clara: el alcance a nivel de operación no se superpone entre solicitudes. Para tres solicitudes concurrentes, se crean tres instancias de servicio aisladas — una por solicitud.

import { Injectable, Scope, createModule } from 'graphql-modules';

@Injectable({ scope: Scope.Operation }) // Por solicitud, completamente aislado
class FinanceService {
  constructor(private authContext: AuthContext) {}

  async transferFunds(amount: number) {
    const token = this.authContext.token; // Seguro: ligado solo a esta solicitud
    return await bankApi.post('/transfer', { amount }, {
      headers: { Authorization: `Bearer ${token}` }
    });
  }
}

Sí, hay un pequeño costo de rendimiento — los servicios se instancian por operación en lugar de reutilizarse. Para servicios que manejan decisiones de autenticación o autorización, esta es la mejor opción. Los Singletons deben reservarse para servicios verdaderamente sin estado, independientes de la solicitud: lectores de configuración, cachés con aislamiento propio, gestores de pools de conexiones, etc.

4. Empujar la autenticación a middleware global, no a internals del servicio

En lugar de que los servicios “extraigan” el contexto del usuario mediante un decorador (que depende de que el runtime conecte correctamente ese contexto), valida y adjunta los datos de autorización antes de que la ejecución llegue al contenedor DI. Un plugin de Envelop o middleware de Express es el lugar adecuado para esto:

const useAuthentication = () => ({
  onExecute({ args }) {
    const token = args.contextValue.token;
    if (!token || !isValid(token)) {
      throw new GraphQLError('No autorizado', {
        extensions: { code: 'UNAUTHENTICATED' }
      });
    }
  }
});

Este modelo de “push” elimina completamente la posibilidad de mezcla de contexto a nivel de decorador.

5. Implementar IDs de correlación de solicitudes para detección

Incluso si no puedes parchear de inmediato, puedes detectar la mezcla de contexto en tus logs. Asigna un Request-ID único a cada solicitud entrante e inclúyelo en todas las líneas de log durante el ciclo de vida de esa solicitud. Si tus logs muestran un mismo Request-ID asociado a más de un User-ID, tienes evidencia de contaminación cruzada de contexto.

app.use((req, res, next) => {
  req.requestId = crypto.randomUUID();
  res.setHeader('X-Request-ID', req.requestId);
  next();
});

La lección más amplia: El estado compartido en entornos asíncronos es peligroso

CVE-2025-59466 y el riesgo de mezcla de contexto en los Módulos GraphQL apuntan a la misma verdad fundamental: el seguimiento del contexto asíncrono es difícil de hacer bien, y las abstracciones “mágicas” construidas sobre ello heredan esa fragilidad.

La propia documentación de Node.js ahora marca la API cruda de async_hooks como experimental y recomienda no usarla. El equipo de Node.js desaconseja encarecidamente su uso, señalando que los casos de uso de seguimiento de contexto asíncrono se resuelven mejor con la API estable AsyncLocalStorage. Frameworks construidos sobre async_hooks antes de que AsyncLocalStorage madurara llevan deuda técnica que puede manifestarse como vulnerabilidades de seguridad bajo condiciones adversas de concurrencia.

La regla para los desarrolladores es simple y absoluta: el estado que pertenece a un usuario nunca debe vivir en un Singleton.

Si estás usando @ExecutionContext hoy, audita cada proveedor Singleton que lo utilice. Pregúntate: ¿qué pasa si esta referencia de contexto es incorrecta? Si la respuesta involucra tokens de autenticación, verificaciones de permisos o datos sensibles, ese servicio no debe ser un Singleton.


Para profundizar

La falla de mezcla de contexto es una de varias amenazas emergentes en el ecosistema GraphQL. Otras áreas que vale la pena auditar en tu pila incluyen ataques por complejidad de consultas (profundidad ilimitada que lleva a DoS), exploits de batching mediante alias nativos de GraphQL, exposición de introspección en producción, y la clase más amplia de bugs TOCTOU en cualquier framework DI que cruce estado de singleton y por solicitud a través de un límite asíncrono.

Tus resolvers solo son tan seguros como el contexto en el que confían.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#Context mixing, GraphQL context mixing, CVE-2026-23735, GraphQL Modules vulnerability, @ExecutionContext, GraphQL race condition, GraphQL concurrency bug, authentication context confusion, privilege escalation GraphQL, parallel request vulnerability, request context leak, security context mixup, cross-request contamination, GraphQL authorization bypass, GraphQL auth bug, GraphQL multi-tenant vulnerability, GraphQL isolation failure, async context bug, thread safety bug GraphQL, Node.js async context leak, request scope vulnerability, dependency injection context bug, GraphQL framework vulnerability, GraphQL gateway security, API context poisoning, API race condition exploit, accidental privilege escalation, low to admin escalation, context propagation bug, request lifecycle bug, GraphQL server misconfiguration, GraphQL execution pipeline bug, parallel resolver execution risk, GraphQL security 2026, GraphQL CVE analysis, GraphQL auth middleware flaw, security decorator bug, ExecutionContext exploit, cross-user data leak GraphQL, GraphQL session confusion, token mixup vulnerability, JWT context bug, access control race condition, TOCTOU GraphQL, concurrent request attack, API privilege escalation, GraphQL threat model, GraphQL pentesting, GraphQL red team, GraphQL blue team defense, fix context isolation, per-request context isolation, secure GraphQL architecture, framework-level auth bug, microservices context leak, multi-user concurrency bug, API trust boundary failure, GraphQL incident response, GraphQL patching guidance

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles