CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-H3HW-29FV-2X75

Context Bleeding: The Race Condition in Envelop GraphQL Modules

Alon Barad
Alon Barad
Software Engineer

Feb 15, 2026·8 min read·28 visits

Executive Summary (TL;DR)

The `@envelop/graphql-modules` plugin failed to properly isolate request contexts, leading to a race condition. Under load, Request A can access Request B's data (and vice-versa), causing severe cross-tenant data leaks and privilege escalation.

A high-severity race condition in the `@envelop/graphql-modules` plugin allows for context bleeding between concurrent GraphQL requests. Due to improper lifecycle management of dependency injection containers, the plugin fails to isolate execution contexts in asynchronous environments. This results in a scenario where a user's request might inadvertently access or modify the session data, authentication tokens, or internal state of another simultaneous user's request. This vulnerability essentially negates previous thread-safety fixes in the core `graphql-modules` library when used within the Envelop ecosystem.

The Hook: When Singletons Get Lonely

In the world of Node.js, we often lull ourselves into a false sense of security because, "Hey, it's single-threaded! I don't need to worry about mutexes or semaphores!" While true that you don't have thread contention in the CPU sense, you absolutely have concurrency contention in the event loop. And nowhere is this more dangerous than when you mix Dependency Injection (DI) with GraphQL.

@envelop/graphql-modules is the glue that binds the powerful module system of graphql-modules to the Envelop execution pipeline (used by tools like GraphQL Yoga). The goal is noble: allow developers to inject request-scoped data (like currentUser or authToken) into their resolvers cleanly. It’s supposed to be a private booth for every request.

But here's the catch: the plugin was managing the velvet rope to that private booth manually. Instead of letting the core library handle the guest list, the plugin decided to manually create and destroy the "operation controllers" (the DI containers). In a high-concurrency environment, this manual management failed to respect the asynchronous boundaries of the runtime. The result? You walk into your private booth, and someone else is already sitting there, drinking your champagne and reading your emails.

The Flaw: A Tale of Two Contexts

To understand this bug, you have to understand the architecture of graphql-modules. It uses a heavy reliance on Dependency Injection. When a request comes in, the library is supposed to spin up a transient, request-scoped container that holds data relevant only to that specific execution.

The vulnerability, tracked as GHSA-H3HW-29FV-2X75, is effectively a regression or bypass of a previous fix (CVE-2026-23735). The core graphql-modules library had already implemented complex logic to ensure that context didn't bleed across async ticks. However, the @envelop/graphql-modules plugin bypassed these safeguards by manually instantiating the controller.

The flaw resided in the onContextBuilding hook. The plugin would create an OperationController and attach it to the context. It assumed that the context object passed into the resolvers would remain referentially unique and isolated throughout the entire async execution chain. However, in the Node.js event loop, when you have heavy concurrency (multiple requests hitting await points simultaneously), the internal pointer to the "current injector" got crossed.

Because the plugin was manually driving the lifecycle (createOperationController -> destroy), it missed the subtle internal wiring required to keep the AsyncLocalStorage (or its polyfill) coherent. This meant that when Resolver A yielded to the event loop to fetch data from a database, and Resolver B started executing, Resolver B might overwrite the shared singleton state. When Resolver A woke up, it would see Resolver B's data.

The Code: Manual Transmission vs. Autopilot

The fix is a textbook example of "stop trying to be smart and let the framework handle it." The developers moved from manually managing the controller lifecycle to using the framework's native wrappers.

The Vulnerable Pattern

In the vulnerable version (< 9.1.0), the code looked something like this. Notice how it manually hooks into onContextBuilding and tries to manage the controller itself:

// VULNERABLE CODE
import { useGraphQLModules } from '@envelop/graphql-modules';
 
// Inside the plugin definition
onContextBuilding({ extendContext, context }) {
  // Manual creation of the controller. 
  // This is where the race begins.
  const controller = app.createOperationController({
    context,
    autoDestroy: false 
  });
  
  // Manually extending the context. 
  // This object is not properly protected against async bleed.
  extendContext({
    ...controller.context,
    [graphqlModulesControllerSymbol]: controller 
  });
}
 
onExecuteDone({ context }) {
  // Trying to clean up manually after execution.
  // Too little, too late if the bleed happened during execution.
  const controller = context[graphqlModulesControllerSymbol];
  controller?.destroy();
}

The Fix

In version 9.1.0 (commit ab49fa2), the entire manual lifecycle management was ripped out. Instead, they utilized app.createExecution(). This method wraps the entire execution function, ensuring that the graphql-modules internal context management (likely using proper closure binding or AsyncLocalStorage) is active for the duration of the call.

// PATCHED CODE
onExecute({ setExecuteFn, executeFn }) {
  // Wrap the execution function.
  // This ensures the framework manages the scope boundary.
  setExecuteFn(
    app.createExecution({
      execute: executeFn,
    }),
  );
},
 
onSubscribe({ setSubscribeFn, subscribeFn }) {
  // Same thing for subscriptions.
  setSubscribeFn(
    app.createSubscription({
      subscribe: subscribeFn,
    }),
  );
}

By delegating the execution wrapping to app.createExecution, the plugin ensures that the correct injector is associated with the current async resource, effectively closing the race window.

The Exploit: Crashing the Party

Exploiting this requires nothing more than timing. We don't need buffer overflows or complex gadget chains. we just need to send two requests at the same time and hope the server mixes them up.

Imagine a GraphQL schema where we can query me { username }.

  1. Attacker Request A: Sends a query but uses a sleep directive or hits a slow resolver. This request is authenticated as attacker.
  2. Victim Request B: Sends a query immediately after. This request is authenticated as admin.

In a vulnerable setup, if both requests hit a service injecting @ExecutionContext(), the internal state might get swapped during the await of Request A.

The Proof of Concept

Here is a simplified reproduction script. If the vulnerability exists, the requestId echoed back will mismatch the one sent in the headers.

const axios = require('axios');
const assert = require('assert');
 
const TARGET = 'http://localhost:4000/graphql';
 
// A query that reflects a value from the context
const QUERY = `
  query CheckContext {
    myContextValue
  }
`;
 
async function sendRequest(id, delay) {
  try {
    const response = await axios.post(TARGET, 
      { query: QUERY },
      {
        headers: { 'x-request-id': id, 'x-delay': delay }
      }
    );
    
    const returnedId = response.data.data.myContextValue;
    
    if (returnedId !== id) {
      console.error(`[CRITICAL] BLEED DETECTED! Sent: ${id}, Got: ${returnedId}`);
      return true;
    } else {
      console.log(`[SAFE] Request ${id} is clean.`);
      return false;
    }
  } catch (e) {
    console.error(e.message);
    return false;
  }
}
 
// Fire two requests concurrently
(async () => {
  console.log("Firing race condition check...");
  const results = await Promise.all([
    sendRequest('REQ_A_SLOW', 500), // Delays inside resolver
    sendRequest('REQ_B_FAST', 0)    // Returns immediately
  ]);
 
  if (results.some(r => r)) {
    console.log("Vulnerability confirmed.");
  }
})();

If the server is vulnerable, REQ_A_SLOW might wake up from its delay and find that its context now contains the ID REQ_B_FAST because the singleton service was overwritten by the second request.

The Impact: Identity Theft at Scale

The impact of this vulnerability is insidious because it is silent. There are no crash logs. There are no error stack traces indicating a failure. The application simply returns the wrong data to the wrong user.

Data Exfiltration

In a multi-tenant SaaS application, this is a nightmare scenario. Tenant A queries for their invoices. Due to the race condition, the tenantId in the context is swapped with Tenant B's tenantId. Tenant A receives a list of Tenant B's invoices.

Privilege Escalation

If the context is used to store authorization roles (e.g., user.isAdmin), an attacker could potentially escalate privileges. By timing their requests to coincide with an administrator's activity (perhaps triggered by a CSRF or social engineering), the attacker's request could inherit the isAdmin: true flag from the concurrent admin request.

The "None" Confidentiality Paradox

The CVSS score indicates Confidentiality: None in some automated assessments because the vulnerability itself is an integrity issue (state corruption). However, do not be fooled. In practice, integrity corruption of an authentication context leads directly to confidentiality loss. If I can become you, I can see your secrets.

Mitigation: Patching the Leak

The remediation is straightforward: Update immediately.

This is not a configuration issue you can tweak away. The flaw is in the compiled code of the plugin. You must upgrade @envelop/graphql-modules to version 9.1.0 or higher.

Remediation Steps

  1. Identify: Check your package.json or yarn.lock for @envelop/graphql-modules. usage.
  2. Update: Run npm install @envelop/graphql-modules@latest or yarn upgrade @envelop/graphql-modules.
  3. Verify: Ensure the installed version is >= 9.1.0.

Defense in Depth

As a developer, you should also look at how you are using Dependency Injection. Avoid storing stateful data in Singleton services unless absolutely necessary. Prefer passing the context object explicitly through resolver arguments rather than relying on implicit injection if your framework's thread-safety is in doubt.

Additionally, implement canary tests in your CI/CD pipeline that specifically target race conditions. Use tools like k6 or custom scripts (like the PoC above) to hammer your endpoints with concurrent requests asserting that the output matches the input identity.

Official Patches

The GuildCommit fixing the race condition

Fix Analysis (1)

Technical Appendix

CVSS Score
8.7/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N

Affected Systems

Node.jsEnvelopGraphQL Yoganpm

Affected Versions Detail

Product
Affected Versions
Fixed Version
@envelop/graphql-modules
The Guild
< 9.1.09.1.0
AttributeDetail
CWECWE-362 (Race Condition)
CVSS v4.08.7 (High)
Attack VectorNetwork
Privileges RequiredNone
ImpactContext/Session Bleeding
Affected Component@envelop/graphql-modules plugin

MITRE ATT&CK Mapping

T1557Adversary-in-the-Middle
Collection
T1539Steal Web Session Cookie
Credential Access
CWE-362
Concurrent Execution using Shared Resource with Improper Synchronization

The application contains a race condition that allows concurrent execution using shared resources with improper synchronization, leading to context bleeding.

Known Exploits & Detection

GitHubThe commit history reveals the fix moving from manual controller management to framework-native execution wrappers.

Vulnerability Timeline

Core library vulnerability (CVE-2026-23735) disclosed
2026-01-16
Fix committed to Envelop repo
2026-01-20
GHSA-H3HW-29FV-2X75 published
2026-01-21

References & Sources

  • [1]GHSA-H3HW-29FV-2X75
  • [2]Related Vulnerability (GHSA-53wg-r69p-v3r7)
Related Vulnerabilities
CVE-2026-23735

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 7 hours ago•CVE-2026-39922
6.3

CVE-2026-39922: Server-Side Request Forgery in GeoNode Service Registration Endpoint

GeoNode versions prior to 4.4.5 and 5.0.2 are vulnerable to Server-Side Request Forgery (SSRF) in the service registration endpoint. Authenticated attackers with low privileges can exploit insufficient input validation in the Web Map Service (WMS) registration module to force the application server to make outbound network queries to loopback addresses, private RFC1918 subnets, link-local scopes, and cloud metadata endpoints. This technical report details the mechanics of the vulnerability, the underlying architectural flaw, and how to effectively remediate and mitigate the associated security risks.

Alon Barad
Alon Barad
4 views•7 min read
•about 16 hours ago•CVE-2022-0492
7.8

CVE-2022-0492: Privilege Escalation and Container Escape via cgroups v1 release_agent

CVE-2022-0492 is a high-severity missing authorization vulnerability in the Linux kernel's Control Groups (cgroups) v1 implementation. The flaw resides within the cgroup_release_agent_write function in kernel/cgroup/cgroup-v1.c, where the kernel fails to validate if the process writing to the release_agent file possesses administrative capabilities in the initial user namespace. This allows a local attacker inside a container with root privileges (UID 0) to abuse user namespaces, mount a cgroups v1 directory, modify the release_agent parameter, and execute arbitrary commands on the host system as host root, effectively achieving a complete container escape.

Amit Schendel
Amit Schendel
8 views•7 min read
•2 days ago•GHSA-G72G-R7M4-9X4G
6.3

GHSA-G72G-R7M4-9X4G: Insufficient Session Expiration of OAuth Tokens in NocoDB

NocoDB is subject to an insufficient session expiration vulnerability where OAuth access and refresh tokens are not invalidated or revoked during security-sensitive actions such as password changes, forgot-password requests, or password resets. This allows an attacker possessing an active OAuth token to maintain unauthorized persistence.

Amit Schendel
Amit Schendel
10 views•6 min read
•3 days ago•GHSA-FGMC-2HQJ-86V4
6.9

GHSA-FGMC-2HQJ-86V4: Default Administrative Credentials in vantage6-server

A vulnerability in the vantage6 federated learning framework allows unauthenticated remote attackers to gain administrative control of the server via hardcoded default credentials (root/root) when deployed under default configurations in versions 4.2.3 and below.

Amit Schendel
Amit Schendel
8 views•5 min read
•3 days ago•GHSA-X9F6-9RVM-MMRG
6.9

GHSA-X9F6-9RVM-MMRG: Improper Access Control and Volume Mount Isolation Bypass in vantage6 Node

An improper access control vulnerability in the vantage6 node component allows concurrently running algorithm containers to read and modify sensitive input and output files of other tasks. The lack of strict workspace directory isolation exposes a significant attack surface in multi-tenant or federated environments where untrusted algorithms are executed.

Amit Schendel
Amit Schendel
3 views•4 min read
•3 days ago•CVE-2026-47760
8.7

CVE-2026-47760: Cross-Site Scripting (XSS) via SVG Namespace Sanitizer Bypass in TinyMCE

TinyMCE versions 6.8.0 through 7.0.1 contain a high-severity Cross-Site Scripting (XSS) vulnerability. The flaw exists in the custom HTML parser and sanitizer module, which incorrectly manages SVG namespace scopes when parsing nested elements. A low-privileged or unauthenticated attacker can submit a crafted HTML payload containing nested SVG structures to bypass sanitization filters, leading to arbitrary JavaScript execution in the context of the victim's browser session.

Alon Barad
Alon Barad
30 views•7 min read