CVE-2026-118665
GraphQL Tools: Per-request connection parameters persist into later users' WebSocket authentications
GraphQL Tools provides utilities for building, stitching, and mocking GraphQL schemas. Prior to version 1.1.36 of the GraphQL Tools legacy WebSocket executor package, buildWSLegacyExecutor in packages/executors/legacy-ws/src/index.ts retains request.extensions.connectionParams in the closure-scoped executorConnectionParams object after cleanupWebsocket closes a connection. When one executor is reused across different authorization contexts, a later request that omits a sensitive key can send an earlier caller's Authorization or tenant value in its connection_init payload, causing the downstream server to authenticate the later caller as the earlier user. Applications that create a new executor for every request are not affected. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.2
GraphQL Tools: Per-request connection parameters persist into later users' WebSocket authentications
GraphQL Tools provides utilities for building, stitching, and mocking GraphQL schemas. Prior to version 1.1.36 of the GraphQL Tools legacy WebSocket executor package, buildWSLegacyExecutor in packages/executors/legacy-ws/src/index.ts retains request.extensions.connectionParams in the closure-scoped executorConnectionParams object after cleanupWebsocket closes a connection. When one executor is reused across different authorization contexts, a later request that omits a sensitive key can send an earlier caller's Authorization or tenant value in its connection_init payload, causing the downstream server to authenticate the later caller as the earlier user. Applications that create a new executor for every request are not affected. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
CVE-2026-118665- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-613
- Updated
- 2026-11-03
EUVDEUVD-2026-112472
8.2
GraphQL Tools provides utilities for building, stitching, and mocking GraphQL schemas. Prior to version 1.1.36 of the GraphQL Tools legacy WebSocket executor package, buildWSLegacyExecutor in packages/executors/legacy-ws/src/index.ts retains request.extensions.connectionParams in the closure-scoped executorConnectionParams object after cleanupWebsocket closes a connection. When one executor is reused across different authorization contexts, a later request that omits a sensitive key can send an earlier caller's Authorization or tenant value in its connection_init payload, causing the downstream server to authenticate the later caller as the earlier user. Applications that create a new executor for every request are not affected. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
EUVD-2026-112472- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Updated
- 2026-11-03
GitHub AdvisoriesGHSA-434m-5hcj-pxf3
8.2
GraphQL Tools: Per-request connection parameters persist into later users' WebSocket authentications
Stale authentication state in @graphql-tools/executor-legacy-ws: per-request extensions.connectionParams persist in executor closure state and are replayed in a later caller's connection_init
Report metadata
| Field | Value |
| --- | --- |
| Package | @graphql-tools/executor-legacy-ws (npm, pkg:npm/%40graphql-tools/executor-legacy-ws) |
| Repository | https://github.com/ardatan/graphql-tools |
| Component | buildWSLegacyExecutor (packages/executors/legacy-ws/src/index.ts) |
| Vulnerability class | Stale authentication state / credential reuse across requests |
| Verified against | Commit b5d1c36ebb8ed3868260ffee5bbcbb423ad5c1f1; npm release 1.1.33 installed and exercised. During validation the same behaviour was also observed on the current latest release 1.1.35, and the unreset closure is still present on master. |
| API surface | Public documented API (buildWSLegacyExecutor, and request.extensions.connectionParams on the returned executor) |
| Affected in default configuration | Yes — the factory returns a general, reusable Executor with no per-identity or single-use restriction, and no supported teardown clears the retained parameters. |
Version note: the originally recorded affected range was <= 1.1.33. Validation showed that range understates exposure — the same proof of concept leaks identically on 1.1.35.
Summary
buildWSLegacyExecutor keeps a single closure-scoped object, executorConnectionParams, for the lifetime of the executor. Every execution that carries request.extensions.connectionParams merges those keys into that object with Object.assign, and the object is merged into the connection_init payload each time a new WebSocket opens. Nothing ever removes keys: cleanupWebsocket terminates the socket and nulls the handle but leaves executorConnectionParams intact, and a later request that simply omits a key skips the conditional update entirely.
The consumer-side shape is a shared executor that forwards per-user credentials. When one long-lived executor serves multiple principals — the schema-stitching, gateway, or mesh pattern where each ExecutionRequest supplies its own extensions.connectionParams — a request that omits the auth key is sent to the downstream server with the previous caller's Authorization/tenant values in its connection_init, so the downstream server authenticates the later caller as the earlier user.
Behaviour of the neighbouring state in the same file:
- WebSocket handle (
websocket) → reset on teardown incleanupWebsocket, lines 78-88 - Per-request
connectionParamsmerge, present case → applied as documented, lines 93-101 executorConnectionParamsclosure object → never reset; survives socket teardown and is re-sent on the next connection, line 32 and line 54
Impact
A caller who supplies no credentials (or lower-privileged ones) can have its downstream GraphQL subscription executed under a previous user's retained token or tenant value, and can receive data that its own connection_init would not authorize. The credential itself is never disclosed to the attacker — it reaches the sink through closure storage, not because the attacker knows it — but the downstream authorization it represents is.
The new capability versus safe behaviour: without this bug, an anonymous request would open a connection whose connection_init contains only base options…
- Record
GHSA-434m-5hcj-pxf3- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-613
- Updated
- 2026-11-02
NVD
8.2
GraphQL Tools provides utilities for building, stitching, and mocking GraphQL schemas. Prior to version 1.1.36 of the GraphQL Tools legacy WebSocket executor package, buildWSLegacyExecutor in packages/executors/legacy-ws/src/index.ts retains request.extensions.connectionParams in the closure-scoped executorConnectionParams object after cleanupWebsocket closes a connection. When one executor is reused across different authorization contexts, a later request that omits a sensitive key can send an earlier caller's Authorization or tenant value in its connection_init payload, causing the downstream server to authenticate the later caller as the earlier user. Applications that create a new executor for every request are not affected. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
CVE-2026-118665- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Weakness
- CWE-613
- Updated
- 2026-11-03
OSV
8.2
GraphQL Tools: Per-request connection parameters persist into later users' WebSocket authentications
GraphQL Tools provides utilities for building, stitching, and mocking GraphQL schemas. Prior to version 1.1.36 of the GraphQL Tools legacy WebSocket executor package, buildWSLegacyExecutor in packages/executors/legacy-ws/src/index.ts retains request.extensions.connectionParams in the closure-scoped executorConnectionParams object after cleanupWebsocket closes a connection. When one executor is reused across different authorization contexts, a later request that omits a sensitive key can send an earlier caller's Authorization or tenant value in its connection_init payload, causing the downstream server to authenticate the later caller as the earlier user. Applications that create a new executor for every request are not affected. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
CVE-2026-118665- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-613
- Updated
- 2026-11-07
OSVGHSA-434m-5hcj-pxf3
8.2
GraphQL Tools: Per-request connection parameters persist into later users' WebSocket authentications
Stale authentication state in @graphql-tools/executor-legacy-ws: per-request extensions.connectionParams persist in executor closure state and are replayed in a later caller's connection_init
Report metadata
| Field | Value |
| --- | --- |
| Package | @graphql-tools/executor-legacy-ws (npm, pkg:npm/%40graphql-tools/executor-legacy-ws) |
| Repository | https://github.com/ardatan/graphql-tools |
| Component | buildWSLegacyExecutor (packages/executors/legacy-ws/src/index.ts) |
| Vulnerability class | Stale authentication state / credential reuse across requests |
| Verified against | Commit b5d1c36ebb8ed3868260ffee5bbcbb423ad5c1f1; npm release 1.1.33 installed and exercised. During validation the same behaviour was also observed on the current latest release 1.1.35, and the unreset closure is still present on master. |
| API surface | Public documented API (buildWSLegacyExecutor, and request.extensions.connectionParams on the returned executor) |
| Affected in default configuration | Yes — the factory returns a general, reusable Executor with no per-identity or single-use restriction, and no supported teardown clears the retained parameters. |
Version note: the originally recorded affected range was <= 1.1.33. Validation showed that range understates exposure — the same proof of concept leaks identically on 1.1.35.
Summary
buildWSLegacyExecutor keeps a single closure-scoped object, executorConnectionParams, for the lifetime of the executor. Every execution that carries request.extensions.connectionParams merges those keys into that object with Object.assign, and the object is merged into the connection_init payload each time a new WebSocket opens. Nothing ever removes keys: cleanupWebsocket terminates the socket and nulls the handle but leaves executorConnectionParams intact, and a later request that simply omits a key skips the conditional update entirely.
The consumer-side shape is a shared executor that forwards per-user credentials. When one long-lived executor serves multiple principals — the schema-stitching, gateway, or mesh pattern where each ExecutionRequest supplies its own extensions.connectionParams — a request that omits the auth key is sent to the downstream server with the previous caller's Authorization/tenant values in its connection_init, so the downstream server authenticates the later caller as the earlier user.
Behaviour of the neighbouring state in the same file:
- WebSocket handle (
websocket) → reset on teardown incleanupWebsocket, lines 78-88 - Per-request
connectionParamsmerge, present case → applied as documented, lines 93-101 executorConnectionParamsclosure object → never reset; survives socket teardown and is re-sent on the next connection, line 32 and line 54
Impact
A caller who supplies no credentials (or lower-privileged ones) can have its downstream GraphQL subscription executed under a previous user's retained token or tenant value, and can receive data that its own connection_init would not authorize. The credential itself is never disclosed to the attacker — it reaches the sink through closure storage, not because the attacker knows it — but the downstream authorization it represents is.
The new capability versus safe behaviour: without this bug, an anonymous request would open a connection whose connection_init contains only base options…
- Record
GHSA-434m-5hcj-pxf3- CVSS 4.0
- 8.2 High
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N - Weakness
- CWE-613
- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
@graphql-tools/executor-legacy-wsnpm |
through 1.1.35 | 1.1.36 |
@graphql-tools/executor-legacy-wsnpm |
before 1.1.36 | 1.1.36 |
History
| Recorded | Changed | Sources |
|---|---|---|
| facts | EUVD, NVD | |
| references | CVE List | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-118665 is a high vulnerability (CVSS 8.2) in @graphql-tools Executor-legacy-ws, published October 28, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.39%, the 33rd percentile. Fixed in @graphql-tools/executor-legacy-ws 1.1.36.
References
github.com/ardatan/graphql-tools/security/advisories/GHSA-434m-5hcj-pxf3
github.com/ardatan/graphql-tools/pull/8480
github.com/ardatan/graphql-tools/commit/c2f226e6f68236be9e4a577f29faeaa92547458b
github.com/ardatan/graphql-tools/releases/tag/@graphql-tools/[email protected]
github.com/advisories/GHSA-434m-5hcj-pxf3
github.com/CVEProject/cvelistV5/tree/main/cves/2026/118xxx/CVE-2026-118665.json
nvd.nist.gov/vuln/detail/CVE-2026-118665
github.com/ardatan/graphql-tools
Left out here: the rest of 2 source descriptions. The JSON record has them.