CVE-2026-118666
GraphQL Tools: Shared legacy executor routes one subscription's results to a different caller
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 reuses one WebSocket and replaces its single websocket.onmessage handler whenever a new overlapping subscription starts. The handler delivers LEGACY_WS.DATA frames to the newest observer without comparing data.id with the subscription operation id, so a caller using the same executor can receive another user's authorized subscription payload while the earlier caller's stream is starved. 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
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- Low
Exploit likelihood
What each source reports
CVE List Score used
6.0
GraphQL Tools: Shared legacy executor routes one subscription's results to a different caller
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 reuses one WebSocket and replaces its single websocket.onmessage handler whenever a new overlapping subscription starts. The handler delivers LEGACY_WS.DATA frames to the newest observer without comparing data.id with the subscription operation id, so a caller using the same executor can receive another user's authorized subscription payload while the earlier caller's stream is starved. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
CVE-2026-118666- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:N/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-200
- Updated
- 2026-11-02
EUVDEUVD-2026-112473
6.0
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 reuses one WebSocket and replaces its single websocket.onmessage handler whenever a new overlapping subscription starts. The handler delivers LEGACY_WS.DATA frames to the newest observer without comparing data.id with the subscription operation id, so a caller using the same executor can receive another user's authorized subscription payload while the earlier caller's stream is starved. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
EUVD-2026-112473- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:N/VA:L/SC:N/SI:N/SA:N - Updated
- 2026-11-03
GitHub AdvisoriesGHSA-mp66-8ww2-pcwm
6.0
GraphQL Tools: Shared legacy executor routes one subscription's results to a different caller
Cross-tenant subscription data leak in @graphql-tools/executor-legacy-ws: a shared websocket.onmessage is replaced on every subscribe(), and DATA frames are delivered without checking the protocol operation id
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 → the returned executor's subscribe(observer) / websocket.onmessage handler |
| Vulnerability class | Cross-tenant data leak (subscription results delivered to the wrong caller) |
| Verified against | Source at b5d1c36ebb8ed3868260ffee5bbcbb423ad5c1f1 (package version 1.1.33); published npm tarball 1.1.33 with matching source at d304214db06e8a4ce9cb41138f199e3bc6ed36f2; the same behaviour also reproduces on the current release 1.1.35 |
| API surface | Public documented API (buildWSLegacyExecutor) |
| Affected in default configuration | Yes — ensureWebsocket() reuses an already-OPEN socket (index.ts:35-42), and the socket has a single mutable onmessage property (index.ts:113). No single-operation-only restriction is documented, and the return type is the general reusable DisposableExecutor (packages/utils/src/executor.ts:34-57) |
Affected version range: the range recorded for this finding is <= 1.1.33, but that is understated — the same proof of concept reproduces on 1.1.35, and a byte diff of the built CJS output between 1.1.33 and 1.1.35 shows only one unrelated change (rejectUnauthorized).
Summary
buildWSLegacyExecutor keeps one WebSocket in closure state and installs the message handler by assigning to the socket's single websocket.onmessage property inside subscribe(). Every new subscription therefore overwrites the previous subscriber's handler, and the handler's case LEGACY_WS.DATA branch calls observer.next(data.payload) without ever comparing data.id to the operation id that this subscription created at line 103 and sent with START at line 123. When one executor instance is reused for overlapping subscriptions, the most recent caller receives payloads belonging to the earlier caller, and the earlier caller receives nothing.
In a consuming application, the tainted input is an ordinary, correctly-tagged DATA frame emitted by the legitimate downstream server for the victim's operation. The attacker does not need to control or compromise that server; they only need to be able to start their own subscription through the same shared executor while the victim's subscription is active — the natural shape of a gateway, stitched schema, or any service that builds a remote executor once and reuses it per request.
The surrounding code shows the inconsistency:
packages/loaders/url/src/index.ts:262-295(in-repoUrlLoaderwrapper) → constructs a fresh legacy executor inside each request, so that caller avoids the shared state.buildWSLegacyExecutor's returned executor, reused across callers → unsafe: sharedonmessageoverwritten at line 113,DATAdispatched without an id check at lines 145-147.packages/loaders/url/src/index.ts:172-182→ also a production caller of the legacy executor; safe only insofar as the wrapper above rebuilds the executor per request, not because of any check in the executor itself.
Impact
A user of a service that shares one legacy executor receives another user's private GraphQL subscription payloads verbatim, with the victim's authorization context, without ever having been authorized for that operation. The victim is simultaneously starved: their own iterator stops receiving the events they subscribed to, because their handler has been detached.
Without this bug, the attacker's observer would receive only results for their own operation — and in this implementation, no result at all, since their operation is never STARTed after…
- Record
GHSA-mp66-8ww2-pcwm- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:N/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-200
- Updated
- 2026-11-02
NVD
6.0
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 reuses one WebSocket and replaces its single websocket.onmessage handler whenever a new overlapping subscription starts. The handler delivers LEGACY_WS.DATA frames to the newest observer without comparing data.id with the subscription operation id, so a caller using the same executor can receive another user's authorized subscription payload while the earlier caller's stream is starved. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
CVE-2026-118666- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:N/VA:L/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-200
- Updated
- 2026-11-03
OSV
6.0
GraphQL Tools: Shared legacy executor routes one subscription's results to a different caller
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 reuses one WebSocket and replaces its single websocket.onmessage handler whenever a new overlapping subscription starts. The handler delivers LEGACY_WS.DATA frames to the newest observer without comparing data.id with the subscription operation id, so a caller using the same executor can receive another user's authorized subscription payload while the earlier caller's stream is starved. This issue is fixed in version 1.1.36 of the GraphQL Tools legacy WebSocket executor package.
- Record
CVE-2026-118666- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:N/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-200
- Updated
- 2026-11-04
OSVGHSA-mp66-8ww2-pcwm
6.0
GraphQL Tools: Shared legacy executor routes one subscription's results to a different caller
Cross-tenant subscription data leak in @graphql-tools/executor-legacy-ws: a shared websocket.onmessage is replaced on every subscribe(), and DATA frames are delivered without checking the protocol operation id
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 → the returned executor's subscribe(observer) / websocket.onmessage handler |
| Vulnerability class | Cross-tenant data leak (subscription results delivered to the wrong caller) |
| Verified against | Source at b5d1c36ebb8ed3868260ffee5bbcbb423ad5c1f1 (package version 1.1.33); published npm tarball 1.1.33 with matching source at d304214db06e8a4ce9cb41138f199e3bc6ed36f2; the same behaviour also reproduces on the current release 1.1.35 |
| API surface | Public documented API (buildWSLegacyExecutor) |
| Affected in default configuration | Yes — ensureWebsocket() reuses an already-OPEN socket (index.ts:35-42), and the socket has a single mutable onmessage property (index.ts:113). No single-operation-only restriction is documented, and the return type is the general reusable DisposableExecutor (packages/utils/src/executor.ts:34-57) |
Affected version range: the range recorded for this finding is <= 1.1.33, but that is understated — the same proof of concept reproduces on 1.1.35, and a byte diff of the built CJS output between 1.1.33 and 1.1.35 shows only one unrelated change (rejectUnauthorized).
Summary
buildWSLegacyExecutor keeps one WebSocket in closure state and installs the message handler by assigning to the socket's single websocket.onmessage property inside subscribe(). Every new subscription therefore overwrites the previous subscriber's handler, and the handler's case LEGACY_WS.DATA branch calls observer.next(data.payload) without ever comparing data.id to the operation id that this subscription created at line 103 and sent with START at line 123. When one executor instance is reused for overlapping subscriptions, the most recent caller receives payloads belonging to the earlier caller, and the earlier caller receives nothing.
In a consuming application, the tainted input is an ordinary, correctly-tagged DATA frame emitted by the legitimate downstream server for the victim's operation. The attacker does not need to control or compromise that server; they only need to be able to start their own subscription through the same shared executor while the victim's subscription is active — the natural shape of a gateway, stitched schema, or any service that builds a remote executor once and reuses it per request.
The surrounding code shows the inconsistency:
packages/loaders/url/src/index.ts:262-295(in-repoUrlLoaderwrapper) → constructs a fresh legacy executor inside each request, so that caller avoids the shared state.buildWSLegacyExecutor's returned executor, reused across callers → unsafe: sharedonmessageoverwritten at line 113,DATAdispatched without an id check at lines 145-147.packages/loaders/url/src/index.ts:172-182→ also a production caller of the legacy executor; safe only insofar as the wrapper above rebuilds the executor per request, not because of any check in the executor itself.
Impact
A user of a service that shares one legacy executor receives another user's private GraphQL subscription payloads verbatim, with the victim's authorization context, without ever having been authorized for that operation. The victim is simultaneously starved: their own iterator stops receiving the events they subscribed to, because their handler has been detached.
Without this bug, the attacker's observer would receive only results for their own operation — and in this implementation, no result at all, since their operation is never STARTed after…
- Record
GHSA-mp66-8ww2-pcwm- CVSS 4.0
- 6.0 Medium
CVSS:4.0/AV:N/AC:H/AT:P/PR:L/UI:N/VC:H/VI:N/VA:L/SC:N/SI:N/SA:N - Weakness
- CWE-200
- 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, OSV | |
| 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-118666 is a medium vulnerability (CVSS 6.0) 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.34%, the 27th percentile. Fixed in @graphql-tools/executor-legacy-ws 1.1.36.
References
github.com/ardatan/graphql-tools/security/advisories/GHSA-mp66-8ww2-pcwm
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-mp66-8ww2-pcwm
github.com/CVEProject/cvelistV5/tree/main/cves/2026/118xxx/CVE-2026-118666.json
nvd.nist.gov/vuln/detail/CVE-2026-118666
github.com/ardatan/graphql-tools
Left out here: the rest of 2 source descriptions. The JSON record has them.