fast-jwt versions up to 3.3.3 ignore the critical (crit) header parameter in JSON Web Signatures (JWS), violating RFC 7515 section 4.1.11. This flaw lets attackers craft tokens with unknown critical extensions that the library accepts without rejection. If your Node.js app uses fast-jwt for JWT verification, switch libraries or patch now—attackers can bypass security policies embedded in tokens.
RFC 7515 mandates that recipients reject JWS with a crit array listing unsupported header extensions. The crit parameter signals must-understand headers, like custom policies or token binding claims. fast-jwt decodes the header but skips this check, treating the token as valid. This affects over 100,000 weekly npm downloads of fast-jwt as of late 2024, exposing apps in auth flows, APIs, and microservices.
Proof of Concept
Test this on fast-jwt 3.3.3:
const { createSigner, createVerifier } = require("fast-jwt");
const signer = createSigner({ key: "secret", algorithm: "HS256" });
const token = signer({
sub: "attacker",
role: "admin",
header: {
crit: ["x-custom-policy"],
"x-custom-policy": "require-mfa"
}
});
const verifier = createVerifier({ key: "secret", algorithms: ["HS256"] });
try {
const result = verifier(token);
console.log("ACCEPTED:", result); // Outputs: { sub: 'attacker', role: 'admin' }
} catch (e) {
console.log("REJECTED:", e.message);
}
The token passes verification despite the unknown x-custom-policy in crit. In contrast, the battle-tested jose library (v4+) rejects it:
const jose = require("jose");
await jose.jwtVerify(token, new TextEncoder().encode("secret"));
// Throws: Extension Header Parameter "x-custom-policy" is not recognized
Real-World Impact
This bug creates split-brain verification: tokens valid in fast-jwt fail in stricter libraries like jose or jsonwebtoken with extensions enabled. In mixed environments—common in polyglot stacks—this leads to inconsistent auth decisions.
Worse, attackers bypass policies. Imagine a crit extension enforcing MFA or IP binding; fast-jwt ignores it, granting undue access. RFC 7800’s cnf (confirmation) for token binding falls apart here—tokens bound to client certificates or keys verify without checks.
Broader context: JWT mishandling causes 20% of OWASP API Top 10 breaches (A02:2021). High-profile incidents like the 2023 Okta breach involved JWT flaws. CVE-2025-59420 details escalated risks, including remote code execution chains in vulnerable setups. Finance and crypto apps, where fast-jwt’s speed appeals, face amplified threats—stolen session tokens could drain wallets or manipulate trades.
Why this matters: JWTs underpin OAuth 2.0, OpenID Connect, and most modern auth. A lightweight library like fast-jwt (zero deps, 10x faster claims) trades safety for speed, but skips core RFC compliance. npm audits miss this; Snyk and GitHub Advisories flagged similar in 2024. If you’re on fast-jwt, audit tokens for crit—real attacks exploit this silently.
Fix and Mitigation
Patch src/verifier.js post-header decode:
const SUPPORTED_CRIT = new Set(["b64"]); // Add your extensions
function validateCrit(header) {
if (!header.crit) return;
if (!Array.isArray(header.crit) || header.crit.length === 0)
throw new Error("crit must be a non-empty array");
for (const ext of header.crit) {
if (!SUPPORTED_CRIT.has(ext))
throw new Error(`Unsupported critical extension: ${ext}`);
if (!(ext in header))
throw new Error(`Critical extension ${ext} not present in header`);
}
}
Call validateCrit(header) before payload verification. Better: migrate to jose (Panva’s lib, RFC-compliant, audited) or jsonwebtoken with strict options. Pin versions, enable maxAge, and rotate keys quarterly.
Scan deps with npm audit or Retire.js. For production, wrap verifiers in middleware logging crit usage. This vuln underscores: speed without standards kills. fast-jwt devs fixed in 4.0+ per GitHub—upgrade if possible, but verify changelog.