{"id":"GHSA-53wg-r69p-v3r7","summary":"GraphQL Modules has a Race Condition issue","details":"### Summary\nOriginally reported as an issue #2613 but should be elevated to a security issue as the ExecutionContext is often used to pass authentication tokens from incoming requests to services loading data from backend APIs.\n\n### Details\nWhen 2 or more parallel requests are made which trigger the same service, the context of the requests is mixed up in the service when the context is injected via `@ExecutionContext()`\n\n### PoC\n\nIn a new project/folder, create and install the following `package.json`:\n\n```json\n{\n  \"name\": \"GHSA-53wg-r69p-v3r7\",\n  \"scripts\": {\n    \"test\": \"jest\"\n  },\n  \"dependencies\": {\n    \"graphql-modules\": \"2.4.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n    \"@babel/plugin-proposal-decorators\": \"^7.28.6\",\n    \"babel-plugin-parameter-decorator\": \"^1.0.16\",\n    \"jest\": \"^29.7.0\",\n    \"reflect-metadata\": \"^0.2.2\"\n  }\n}\n```\n\nwith:\n\n```\nnpm i\n```\n\nconfigure `babel.config.json` using:\n\n```json\n{\n  \"plugins\": [\n    [\"@babel/plugin-proposal-decorators\", { \"legacy\": true }],\n    \"babel-plugin-parameter-decorator\",\n    \"@babel/plugin-proposal-class-properties\"\n  ]\n}\n```\n\nthen write the following test `GHSA-53wg-r69p-v3r7.spec.ts`:\n\n```js\nrequire(\"reflect-metadata\");\nconst {\n  createApplication,\n  createModule,\n  Injectable,\n  Scope,\n  ExecutionContext,\n  gql,\n  testkit,\n} = require(\"graphql-modules\");\n\ntest(\"accessing a singleton provider context during another asynchronous execution\", async () =\u003e {\n  @Injectable({ scope: Scope.Singleton })\n  class IdentifierProvider {\n    @ExecutionContext()\n    context;\n\n    getId() {\n      return this.context.identifier;\n    }\n  }\n\n  const { promise: gettingBefore, resolve: gotBefore } = createDeferred();\n\n  const { promise: waitForGettingAfter, resolve: getAfter } = createDeferred();\n\n  const mod = createModule({\n    id: \"mod\",\n    providers: [IdentifierProvider],\n    typeDefs: gql`\n      type Query {\n        getAsyncIdentifiers: Identifiers!\n      }\n\n      type Identifiers {\n        before: String!\n        after: String!\n      }\n    `,\n    resolvers: {\n      Query: {\n        async getAsyncIdentifiers(_0, _1, context) {\n          const before = context.injector.get(IdentifierProvider).getId();\n          gotBefore();\n          await waitForGettingAfter;\n          const after = context.injector.get(IdentifierProvider).getId();\n          return { before, after };\n        },\n      },\n    },\n  });\n\n  const app = createApplication({\n    modules: [mod],\n  });\n\n  const document = gql`\n    {\n      getAsyncIdentifiers {\n        before\n        after\n      }\n    }\n  `;\n\n  const firstResult$ = testkit.execute(app, {\n    contextValue: {\n      identifier: \"first\",\n    },\n    document,\n  });\n\n  await gettingBefore;\n\n  const secondResult$ = testkit.execute(app, {\n    contextValue: {\n      identifier: \"second\",\n    },\n    document,\n  });\n\n  getAfter();\n\n  await expect(firstResult$).resolves.toEqual({\n    data: {\n      getAsyncIdentifiers: {\n        before: \"first\",\n        after: \"first\",\n      },\n    },\n  });\n\n  await expect(secondResult$).resolves.toEqual({\n    data: {\n      getAsyncIdentifiers: {\n        before: \"second\",\n        after: \"second\",\n      },\n    },\n  });\n});\n\nfunction createDeferred() {\n  let resolve, reject;\n  const promise = new Promise((res, rej) =\u003e {\n    resolve = res;\n    reject = rej;\n  });\n  return {\n    promise,\n    resolve,\n    reject,\n  };\n}\n```\n\nand execute using:\n\n```\nnpm test\n```\n\nYour project tree should look like this:\n\n```\nGHSA-53wg-r69p-v3r7\n    package.json\n    package-lock.json\n    babel.config.json\n    GHSA-53wg-r69p-v3r7.spec.js\n```\n\n#### Expected vs. Actual Outcome\n\n```diff\n- Expected  - 1\n+ Received  + 1\n\n  Object {\n    \"data\": Object {\n      \"getAsyncIdentifiers\": Object {\n-       \"after\": \"first\",\n+       \"after\": \"second\",\n        \"before\": \"first\",\n      },\n    },\n  }\n```\n\n### Impact\n\nAny application that uses services that inject the context using `@ExecutionContext()` from a singleton provider are at risk. The more traffic an application has, the higher the chance for parallel requests, the higher the risk.","aliases":["CVE-2026-23735"],"modified":"2026-02-03T03:11:43.933186Z","published":"2026-01-16T21:09:08Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-01-16T21:09:08Z","nvd_published_at":"2026-01-16T20:15:51Z","cwe_ids":["CWE-362"],"severity":"HIGH"},"references":[{"type":"WEB","url":"https://github.com/graphql-hive/graphql-modules/security/advisories/GHSA-53wg-r69p-v3r7"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-23735"},{"type":"WEB","url":"https://github.com/graphql-hive/graphql-modules/issues/2613"},{"type":"WEB","url":"https://github.com/graphql-hive/graphql-modules/pull/2521"},{"type":"PACKAGE","url":"https://github.com/graphql-hive/graphql-modules"},{"type":"WEB","url":"https://github.com/graphql-hive/graphql-modules/releases/tag/release-1768575025568"}],"affected":[{"package":{"name":"graphql-modules","ecosystem":"npm","purl":"pkg:npm/graphql-modules"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.2.1"},{"fixed":"2.4.1"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-53wg-r69p-v3r7/GHSA-53wg-r69p-v3r7.json"}},{"package":{"name":"graphql-modules","ecosystem":"npm","purl":"pkg:npm/graphql-modules"},"ranges":[{"type":"SEMVER","events":[{"introduced":"3.0.0"},{"fixed":"3.1.1"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-53wg-r69p-v3r7/GHSA-53wg-r69p-v3r7.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"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"}]}