Launch Week Day 1: Announcing Security Design Review
HIGH 7.5 npm

fast-jwt accepts unknown `crit` header extensions (RFC 7515 violation)

GHSA-hm7r-c7qw-ghp6 · CVE-2026-35042

Published · Modified

Description

Summary

fast-jwt does not validate the crit (Critical) Header Parameter defined in RFC 7515 §4.1.11. When a JWS token contains a crit array listing extensions that fast-jwt does not understand, the library accepts the token instead of rejecting it. This violates the MUST requirement in the RFC.


RFC Requirement

RFC 7515 §4.1.11:

If any of the listed extension Header Parameters are not understood
and supported
by the recipient, then the JWS is invalid.


Proof of Concept

const { createSigner, createVerifier } = require("fast-jwt"); // v3.3.3

const signer = createSigner({ key: "secret", algorithm: "HS256" });
const token = signer({
  sub: "attacker",
  role: "admin",
  header: { crit: ["x-custom-policy"], "x-custom-policy": "require-mfa" },
});

// Should REJECT — x-custom-policy is not understood
const verifier = createVerifier({ key: "secret", algorithms: ["HS256"] });
try {
  const result = verifier(token);
  console.log("ACCEPTED:", result);
  // Output: ACCEPTED: { sub: 'attacker', role: 'admin' }
} catch (e) {
  console.log("REJECTED:", e.message);
}

Expected: Error — unsupported critical extension
Actual: Token accepted.

Comparison

// jose (panva) v4+ — correctly rejects
const jose = require("jose");
await jose.jwtVerify(token, new TextEncoder().encode("secret"));
// throws: Extension Header Parameter "x-custom-policy" is not recognized

Impact

  • Split-brain verification in mixed-library environments
  • Security policy bypass when crit carries enforcement semantics
  • Token binding bypass (RFC 7800 cnf confirmation)
  • See CVE-2025-59420 for full impact analysis

Suggested Fix

In src/verifier.js, add crit validation after header decoding:

const SUPPORTED_CRIT = new Set(["b64"]);

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`);
  }
}

Ready to move

Start Securing

Free, no credit card | First findings in minutes