GHSA-7p5p-67m9-5c8j: 
JavaScript Analyse et atténuation de la vulnérabilité

Report metadata

FieldValue
Package@graphql-tools/executor-http (npm, pkg:npm/%40graphql-tools/executor-http)
Repositoryhttps://github.com/graphql-hive/gateway
ComponentbuildHTTPExecutor / handleInflightRequest (packages/executors/http/src/index.ts)
Vulnerability classInformation disclosure / authorization bypass via cache (response reuse across security contexts)
CWECWE-524
Severity (reporter assessment)High
Verified againstSource read at tag @graphql-tools/executor-http@3.3.0 (commit 0778df53dd9450c2b1d6fcf121832b198fe75d67); line references below pinned to commit a0d7bd84f2c655b67410831ae8119bc26c351cc4; published npm artifacts 2.1.2, 3.0.0, 3.3.0 and 3.4.1 exercised dynamically
API surfacePublic documented API (buildHTTPExecutor, and the documented fetch option)
Affected in default configurationYes — deduplicateInflightRequests is documented @default true and is only bypassed when explicitly set to false (packages/executors/http/src/index.ts:150-154)
Affected versions>= 3.0.0 through at least 3.4.1. Inflight deduplication landed in 3.0.0 (per the package CHANGELOG); 2.1.2 has no deduplication code and does not leak; 3.0.0, 3.3.0 and the latest release at time of validation, 3.4.1, all reproduce
Fixed versionNone known — no fixed release existed at time of validation
Public advisory / CVE / GHSANone (unknown / not assigned)

Summary

buildHTTPExecutor deduplicates concurrent requests using a module-level Map keyed only by URL, HTTP method, the serialized request body, and — only when isCustomHeader is true — the already-materialized library-visible headers. Neither the ExecutionRequest's context, nor info, nor the identity of the fetch implementation, nor the executor instance participates in that key. The documented fetch hook is explicitly handed each request's context and info, so a hook that derives upstream authorization (for example a per-user bearer token) from context runs only for the first of two concurrent identical queries; the second caller receives the first caller's already-authorized response. In a consuming application, any remote GraphQL caller controls its own document and variables and can therefore choose a query a victim is also making. Because dedup is keyed on the serialized body, the attacker's request selects the victim's inflight Promise before the attacker's own fetch hook can apply the attacker's authorization. The option's own comment promises deduplication of "requests with the same parameters", but two requests with different authorization context are not security-equivalent. Sibling paths in the same code, which is what makes this look like a bug rather than a documented trade-off:

  • Per-user auth expressed through options.headers / request.extensions.headers -> safe: headers join the dedup key when present (index.ts:568-570); confirmed non-leaking in testing.
  • Per-user auth applied inside the documented fetch hook from context/info -> unsafe: context and info never enter the key (index.ts:561-570).
  • deduplicateInflightRequests: false -> safe: each request runs its own fetch with its own context; confirmed non-leaking in testing.

Impact

An attacker who races a query that an authenticated victim is making receives the victim's upstream GraphQL response — data the upstream service authorized for the victim's identity or tenant, not the attacker's. In the validated reproduction this was a cross-tenant authorization bypass: the second user received the first user's record, including a sensitive per-user field, while the upstream service saw only the first user's bearer token. The delta versus safe behaviour: without Promise reuse, fetchFn would run with the attacker's own context, the hook would attach the attacker's token, and the upstream service would return only attacker-visible data. The bug converts a correctly context-enforcing fetch wrapper into a no-op for every deduplicated request. Because the inflight Map is module-level rather than per-executor, the reuse was observed to cross two independent buildHTTPExecutor instances with distinct fetch hooks in the same process. Scoring note: no CVSS vector is asserted here. Exploitability depends on the consuming application's data flow — specifically whether per-request upstream authorization is applied inside the fetch hook (affected) or entirely through library-visible headers (not affected, because those headers enter the dedup key). Consumers that set deduplicateInflightRequests: false, that never pass a custom fetch, or whose upstream responses are identical for all callers are not affected. The reporter assessment of High reflects the unauthenticated-to-authenticated data disclosure in the default configuration for the affected shape.

Preconditions

  1. deduplicateInflightRequests is omitted or true — the documented default is true (index.ts:150-154).
  2. Upstream authorization or tenant selection is applied inside the fetch hook using its context or info argument, rather than being fully represented by options.headers / request.extensions.headers.
  3. Two requests share the same endpoint, operation, variables and library-visible headers while one is still in flight.
  4. The attacker can issue a GraphQL query of their own choosing (its document and variables) against the consuming application and overlap it with the victim's request. This requires no monkey-patching and no non-standard usage. HTTPExecutorOptions.fetch is documented as a supported WHATWG fetch implementation (index.ts:71-76) and its public type explicitly supplies context and info (index.ts:38-54). There is no documented warning that a context-dependent hook must disable deduplication, and the behaviour is not documented as caller responsibility.

<!---

Data flow

  1. Source — two callers (attacker and victim) pass identical document/variables to the executor returned by the public entrypoint, with different context (index.ts:209-212, index.ts:329-337).
  2. Propagation — the fetch implementation selected from request.extensions.fetch / options.fetch is invoked as fetchFn(url, init, context, info), so it can and is intended to depend on per-request context (index.ts:220, index.ts:388-413; observed at src/index.ts:402 at the 3.3.0 tag).
  3. Sink — the dedup key is assembled from URL, method, body and (only when isCustomHeader) the library-visible headers, then inflightRequests.get(inflightRequestId) returns the existing result Promise: let inflightRequest = inflightRequests.get(inflightRequestId); (index.ts:561-570, reuse at index.ts:571-597). The map itself is module-level (index.ts:180), so it is shared across executor instances in the process.
  4. Reachability — buildHTTPExecutor is exported from the package entrypoint (index.ts:182-206) and the returned executor always enters the inflight path (index.ts:624-628, index.ts:651-655). In-repository callers exist at packages/transports/http/src/index.ts:61 and packages/federation/src/supergraph.ts:1685.

Vulnerable code

packages/executors/http/src/index.ts at a0d7bd84f2c655b67410831ae8119bc26c351cc4 — excerpt spanning the module-level map (line 180) and the dedup key construction and Promise reuse (lines ~561-597); ... marks elided code:

const inflightRequests = new Map<InflightRequestId, Promise<ExecutionResult>>(); // module-level: shared across executor instances
...
fetchFn(inflightRequestOptions.url, { ... }, context, info) // hook receives per-request context/info
...
let inflightRequestId = `${inflightRequestOptions.url}|${inflightRequestOptions.method}`;
if (inflightRequestOptions.body) inflightRequestId += `|${inflightRequestOptions.body}`;
if (isCustomHeader) inflightRequestId += `|${JSON.stringify(inflightRequestOptions.headers)}`; // safe sibling: library-visible headers only
let inflightRequest = inflightRequests.get(inflightRequestId); // SINK: key omits context, info, fetch identity, executor identity
if (!inflightRequest) { inflightRequest = runInflightRequest(); ... }
return handleMaybePromise(() => inflightRequest, ...);

Reproduction

Verified dynamically against the published npm artifact (Node v24), not only by static review. A local HTTP subgraph echoed data scoped to the bearer token it received; a 200 ms upstream delay guaranteed overlap; the executor used the documented context-aware pattern, with a fetch hook attaching a per-user token taken from context. Observed on @graphql-tools/executor-http@3.3.0: two concurrent identical queries for two different users invoked the fetch hook once, produced one upstream request carrying only the first user's Bearer token, and the second user received the first user's authorized record, including that user's sensitive per-user field. Controls that isolate the cause:

  • deduplicateInflightRequests: false -> 2 hook calls, 2 upstream requests, correct per-user results.
  • The same two calls issued sequentially (never overlapping in flight) -> correct per-user results.
  • The same token supplied via library-visible options.headers instead of the hook -> no leak, because the headers then enter the dedup key.
  • Version boundary: 2.1.2 (no deduplication code in dist) does not leak; 3.0.0 leaks; 3.3.0 leaks; 3.4.1 (latest release at time of validation) leaks. The minimal shape of the issue, for reading alongside the above:
import { buildHTTPExecutor } from '@graphql-tools/executor-http';
import { parse } from 'graphql';
let release!: () => void;
const gate = new Promise<void>(resolve => { release = resolve; });
const executor = buildHTTPExecutor({
  endpoint: 'https://upstream.example/graphql',
  // A real hook would add Authorization: Bearer context.token before calling fetch.
  async fetch(_url, _init, context) {
    await gate; // keep victim request inflight while attacker sends same query
    return Response.json({ data: { viewer: context.user } });
  },
});
const document = parse('query { viewer }');
const victim = executor({ document, context: { user: 'victim', token: 'victim-token' } });
const attacker = executor({ document, context: { user: 'attacker', token: 'attacker-token' } });
release();
console.log(await attacker); // { data: { viewer: 'victim' } }, not attacker
  • Expected: the two results are independently authorized (each caller's own context reaches fetch), or deduplication requires an explicit security-equivalence key.
  • Actual: the fetch hook runs once with the first caller's context, and the second caller receives the first caller's authorized response.

Suggested remediation

Do not deduplicate across security contexts: the inflight key must either include everything the upstream authorization decision depends on, or deduplication must be skipped when that dependency is invisible to the library. The following sketch is illustrative and was not compiled or tested:

// 1. Keep inflight state per executor instance rather than module-global,
//    so distinct executors (and distinct fetch hooks) never share entries.
export function buildHTTPExecutor(options?: HTTPExecutorOptions): ... {
  const inflightRequests = new Map<InflightRequestId, Promise<ExecutionResult>>();
  ...
}
// 2. Only deduplicate when the caller has told the library what makes two
//    requests security-equivalent; otherwise run each request's own fetch.
const securityKey = options?.inflightRequestSecurityKey?.(request); // new, caller-provided
const canDeduplicate = deduplicateInflightRequests && (securityKey !== undefined || !customFetchCanSeeContext);
if (canDeduplicate && securityKey !== undefined) {
  inflightRequestId += `|${securityKey}`;
}

An alternative, more conservative form in the same idiom: leave the key as it is, but default deduplicateInflightRequests to false whenever a custom fetch is supplied via options.fetch or request.extensions.fetch, and require an explicit opt-in to deduplicate in that case. Notes:

  • isCustomHeader only adds headers the library has already materialized (index.ts:568-570); headers or credentials added inside the fetch hook are not represented in the key at all, so this partitioning cannot be relied on for the hook path.
  • The structuredClone of the shared result (index.ts:589-594) prevents cross-request mutation of a shared object, not disclosure: it copies an already-wrong result.
  • Already safe, and needing no change: consumers that express all per-user authorization through options.headers / request.extensions.headers (headers enter the key), consumers passing no custom fetch, and any caller that sets deduplicateInflightRequests: false.
  • The option's documentation is part of the fix: HTTPExecutorOptions.fetch is documented as receiving context/info with no warning that a context-dependent hook must disable deduplication (index.ts:38-54, index.ts:71-76). Suggested regression test alongside the existing tests for packages/executors/http (the exact spec file is unknown to this report): issue two concurrent, byte-identical requests (same endpoint, document and variables) with different context values through a fetch hook that returns data derived from context, and assert that the hook is invoked twice and each caller receives only its own context's data — plus a second assertion that two separate buildHTTPExecutor instances with different fetch hooks never share an inflight entry. --->

Source: NVD

Connexe JavaScript Vulnérabilités :

CVE ID

Gravité

Partition

Technologies

Nom du composant

Exploit CISA KEV

A corrigé

Date de publication

GHSA-rhhp-7488-3qwfMEDIUM6.5
  • JavaScript logoJavaScript
  • nitropack
NonOuiNov 02, 2026
GHSA-pqxc-r4h5-jc95MEDIUM6.1
  • JavaScript logoJavaScript
  • nitropack
NonOuiNov 02, 2026
GHSA-39r7-wfcx-99xgMEDIUM4.8
  • JavaScript logoJavaScript
  • nitropack
NonOuiNov 02, 2026
GHSA-xrjv-fc7j-8f77HIGHN/A
  • JavaScript logoJavaScript
  • @graphql-tools/batch-execute
NonNonNov 02, 2026
GHSA-7p5p-67m9-5c8jHIGHN/A
  • JavaScript logoJavaScript
  • @graphql-tools/executor-http
NonNonNov 02, 2026

Évaluation gratuite des vulnérabilités

Évaluez votre posture de sécurité cloud

Évaluez vos pratiques de sécurité cloud dans 9 domaines de sécurité afin d’évaluer votre niveau de risque et d’identifier les lacunes dans vos défenses.

Évaluation de la demande

Ressources Wiz supplémentaires

Obtenez une démo personnalisée

Prêt(e) à voir Wiz en action ?

"La meilleure expérience utilisateur que j’ai jamais vue, offre une visibilité totale sur les workloads cloud."
David EstlickRSSI
"Wiz fournit une interface unique pour voir ce qui se passe dans nos environnements cloud."
Adam FletcherChef du service de sécurité
"Nous savons que si Wiz identifie quelque chose comme critique, c’est qu’il l’est réellement."
Greg PoniatowskiResponsable de la gestion des menaces et des vulnérabilités