← Back to Feed CACHED · 2026-08-15 13:54:39 · CACHE_KEY CVE-2026-18500
CVE-2026-18500 · CWE-347 · Disclosed 2026-08-14

@fastify/jwt vulnerable to authorization bypass via global secret overriding the per-request key

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

Like handing out VIP wristbands that the bouncer never actually checks because the house stamp overrides everything

CVE-2026-18500 affects @fastify/jwt versions prior to 10.2.2 (~636K weekly npm downloads). When an application registers a global secret *and* passes a per-request verification key via request.jwtVerify({ verify: { key } }) or reply.jwtSign(payload, { key }), the plugin silently resolves the global secret first and uses it for signature verification, completely ignoring the caller-supplied key. Any JWT signed with the global secret passes verification on routes that were intended to enforce a different, per-request cryptographic boundary. The direct fastify.jwt.verify(token, { key }) API is not affected.

The vendor rates this HIGH 8.1 (CVSS:3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N). That score accurately reflects the network-reachable, low-privilege, high-CI impact *when the per-request key pattern is in use*. However, the score does not account for the fact that many — arguably most — @fastify/jwt consumers simply use a single global secret and never exercise the per-request key API. For those deployments, this CVE is a no-op. noisgate applies a modest downward adjustment to 7.2 to reflect the narrower-than-universal affected population while acknowledging the severity of the impact where it does apply.

"Per-request key isolation silently broken — multi-tenant JWT boundaries are a fiction until you patch."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Obtain valid low-privilege JWT

The attacker authenticates normally against the target application and receives a JWT signed with the application's global secret. This is a standard, sanctioned operation — no exploitation occurs at this step. The attacker simply needs any account.
Conditions required:
  • Target application uses @fastify/jwt < 10.2.2
  • Application exposes account registration or the attacker has compromised a low-privilege account
Where this breaks in practice:
  • Applications behind SSO / IdP federation may not issue JWTs from @fastify/jwt directly
  • Rate limiting on auth endpoints is standard
STEP 02

Identify routes using per-request key verification

The attacker identifies API routes that are intended to enforce a separate JWT verification key — for example, admin endpoints, partner-scoped APIs, or tenant-specific routes. This is typically discoverable via API documentation, OpenAPI specs, or error messages that leak key-mismatch details.
Conditions required:
  • Application uses request.jwtVerify({ verify: { key } }) on at least one route
Where this breaks in practice:
  • Many applications use a single global secret and are not vulnerable
  • Per-request key usage is a deliberate, less common architectural choice — common in multi-tenant SaaS but rare in single-tenant monoliths
STEP 03

Present global-secret JWT on key-protected route

The attacker sends a request to the key-protected route using their legitimately obtained global-secret JWT. Due to the bug, @fastify/jwt resolves the global secret *before* the per-request key and uses it for verification. The signature check passes. The route handler executes as if the attacker had presented a token signed with the intended per-request key.
Conditions required:
  • Vulnerable version of @fastify/jwt in use
  • Route handler does not perform secondary, out-of-band key verification
Where this breaks in practice:
  • Some applications may perform additional claims validation (e.g., iss, aud, tenant_id) that limits cross-boundary access even with a valid signature
  • Applications using asymmetric per-request keys (RS256/ES256) alongside an HMAC global secret may trigger algorithm-mismatch failures in some configurations
Detection/coverage: Application-level logging of JWT kid or iss claims versus expected values may flag anomalous tokens. No CVE-specific scanner signatures observed as of 2026-08-15.
STEP 04

Access unauthorized resources

With the route handler executing under the attacker's token, the attacker can read or modify data belonging to other tenants, escalate from user to admin context, or pivot across authorization boundaries that were intended to be cryptographically enforced. The impact depends entirely on what the per-request key was protecting.
Conditions required:
  • Route handler trusts the JWT verification result without additional authorization checks
Where this breaks in practice:
  • Defense-in-depth authorization layers (e.g., OPA, RBAC middleware checking database-backed permissions) may block the actual data access even if the JWT verification succeeds
Detection/coverage: Audit logs correlating user identity with accessed tenant/resource IDs can surface cross-boundary access patterns.
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNo known exploitation as of 2026-08-15. Not listed on CISA KEV.
Proof-of-conceptNo public PoC repository identified. The advisory was published by the maintainer (@mcollina) and includes a clear description of the trigger pattern (request.jwtVerify with per-request key), making reproduction straightforward for anyone with a test harness.
EPSSNot yet scored (advisory published 2026-08-14). Expected to be low (<5th percentile) given library-level bug requiring specific usage pattern.
KEV statusNot listed. No CISA KEV entry as of 2026-08-15.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N — Network-reachable, low privilege required, no user interaction, high confidentiality and integrity impact, no availability impact. Scope unchanged.
Affected versions@fastify/jwt < 10.2.2. All 10.x, 9.x, and earlier releases using per-request key verification are affected.
Fixed version10.2.2 (released 2026-08-14 via npm)
Scanning / exposureNot applicable for Shodan/GreyNoise — this is a server-side library, not a network-exposed service fingerprint. npm downloads: ~636K/week, indicating broad adoption in the Node.js ecosystem. Exposure is code-level, detectable via SBOM/SCA tooling (Snyk, Socket, npm audit).
Disclosure date2026-08-14 (GHSA-j4cx-787j-xjqg published alongside patch)
ReporterMatteo Collina (@mcollina), Fastify core maintainer — coordinated self-disclosure.
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to HIGH (7.2/10)

The single most decisive factor is the narrow triggering condition: only applications exercising the per-request key API (request.jwtVerify({ verify: { key } })) are affected, which represents a subset of the ~636K weekly download base. The vendor's 8.1 score is technically correct for the impact surface but overestimates the reachable population, warranting a modest reduction to 7.2.

HIGH Vulnerability mechanics and affected version range
MEDIUM Fraction of deployments using per-request key pattern
LOW Exploitation timeline and attacker interest

Why this verdict

  • Narrow affected pattern: The bug only fires when an application uses request.jwtVerify({ verify: { key } }) or reply.jwtSign(payload, { key }). Applications using only the global secret (likely the majority of @fastify/jwt consumers) are completely unaffected, reducing the real-world blast radius.
  • PR:L prerequisite: The attacker needs a valid JWT from the target application — this requires either a legitimate account or a credential compromise, which adds friction and presupposes an initial foothold.
  • Role multiplier: @fastify/jwt is a general-purpose library, not a canonical high-value-role component. However, the per-request key pattern is *specifically* the pattern used for multi-tenant SaaS isolation and admin/user key separation. When it is used, the blast radius is tenant-isolation bypass (cross-tenant data access, privilege escalation across JWT domains). This is a HIGH-floor outcome for the subset of deployments using this pattern, preventing a downgrade below HIGH.
  • No exploitation evidence or public PoC: The advisory is <48 hours old with no known weaponization, reducing urgency but not severity.

Why not higher?

A CRITICAL rating would require either active exploitation, a wider affected population, or a higher blast radius. The vulnerability requires a specific usage pattern that most @fastify/jwt consumers do not exercise. The PR:L requirement means unauthenticated mass exploitation is not possible. There is no availability impact, no code execution — this is an authorization boundary bypass, not RCE.

Why not lower?

When the per-request key pattern *is* in use, the impact is a complete cryptographic authorization bypass — the security boundary the developer explicitly constructed is silently ignored. Multi-tenant SaaS platforms using this pattern face cross-tenant data exposure. The 636K weekly download count means even a small percentage of affected deployments represents thousands of services. The network-reachable, low-complexity attack vector keeps this firmly in HIGH territory.

05 · Compensating Control

What to do — in priority order.

  1. Switch to fastify.jwt.verify(token, { key }) for key-sensitive routes — The direct API is confirmed unaffected by this bug. Refactoring key-sensitive route handlers to use fastify.jwt.verify() instead of request.jwtVerify() eliminates the vulnerability without upgrading. Deploy within 30 days per noisgate mitigation SLA for HIGH.
  2. Add claims-based authorization checks independent of signature verification — Validate iss, aud, kid, or custom tenant_id claims against expected values in route handlers. This defense-in-depth measure blocks cross-boundary access even if the wrong key verifies the signature. Deploy within 30 days.
  3. Run npm audit and SCA scans to identify affected services — Use npm audit, Snyk, or Socket to enumerate all services importing @fastify/jwt < 10.2.2. Prioritize services that use the per-request key pattern. This is a triage step — execute immediately.
  4. Upgrade to @fastify/jwt 10.2.2 — The definitive fix. Version 10.2.2 was released 2026-08-14. Target remediation within 180 days per noisgate remediation SLA for HIGH, but aim for 30 days given the simplicity of the npm update.
What doesn't work
  • WAF / API gateway JWT validation — if the gateway re-verifies tokens using its own key, it may catch mismatches, but most gateways pass through to the application's own verification. The bug is in the application-layer library, not the network path.
  • Rotating the global secret — the vulnerability is in key *selection priority*, not key compromise. A new global secret will still override per-request keys.
  • Rate limiting — the attacker only needs one valid token and one request to exploit the bypass. Rate limiting does not help.
06 · Verification

Crowdsourced verification payload.

Run this on a CI/auditor workstation with Node.js and jq installed. It inspects package-lock.json files to detect vulnerable @fastify/jwt versions. Usage: bash check_cve_2026_18500.sh /path/to/project — no special privileges required.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# CVE-2026-18500 checker for @fastify/jwt
# Checks if the installed version is < 10.2.2
# Usage: bash check_cve_2026_18500.sh /path/to/project
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN

set -euo pipefail

PROJECT_DIR="${1:-.}"
PACKAGE_LOCK="$PROJECT_DIR/package-lock.json"
NODE_MODULES="$PROJECT_DIR/node_modules/@fastify/jwt/package.json"

FIXED_MAJOR=10
FIXED_MINOR=2
FIXED_PATCH=2

check_version() {
  local ver="$1"
  local major minor patch
  major=$(echo "$ver" | cut -d. -f1)
  minor=$(echo "$ver" | cut -d. -f2)
  patch=$(echo "$ver" | cut -d. -f3 | cut -d- -f1)

  if [ "$major" -gt "$FIXED_MAJOR" ]; then
    echo "PATCHED"
    return 0
  elif [ "$major" -eq "$FIXED_MAJOR" ] && [ "$minor" -gt "$FIXED_MINOR" ]; then
    echo "PATCHED"
    return 0
  elif [ "$major" -eq "$FIXED_MAJOR" ] && [ "$minor" -eq "$FIXED_MINOR" ] && [ "$patch" -ge "$FIXED_PATCH" ]; then
    echo "PATCHED"
    return 0
  else
    echo "VULNERABLE"
    return 1
  fi
}

# Try node_modules first
if [ -f "$NODE_MODULES" ]; then
  VERSION=$(jq -r '.version' "$NODE_MODULES" 2>/dev/null)
  if [ -n "$VERSION" ] && [ "$VERSION" != "null" ]; then
    echo "[*] Found @fastify/jwt $VERSION in node_modules"
    check_version "$VERSION"
    exit $?
  fi
fi

# Fall back to package-lock.json
if [ -f "$PACKAGE_LOCK" ]; then
  VERSION=$(jq -r '.packages["node_modules/@fastify/jwt"].version // .dependencies["@fastify/jwt"].version // empty' "$PACKAGE_LOCK" 2>/dev/null)
  if [ -n "$VERSION" ]; then
    echo "[*] Found @fastify/jwt $VERSION in package-lock.json"
    check_version "$VERSION"
    exit $?
  fi
fi

echo "UNKNOWN — @fastify/jwt not found in $PROJECT_DIR"
exit 2
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: Run npm audit across all Node.js services to identify instances of @fastify/jwt < 10.2.2. Prioritize any service that uses request.jwtVerify({ verify: { key } }) or reply.jwtSign(payload, { key }) — those are actively vulnerable to cross-boundary authorization bypass. For those services, either upgrade to 10.2.2 or switch verification calls to fastify.jwt.verify(token, { key }) as a compensating control. Per the noisgate mitigation SLA for HIGH, deploy compensating controls within 30 days. Per the noisgate remediation SLA, complete the full upgrade across all affected services within 180 days. Given the trivial nature of the npm update and the fact that no breaking changes were introduced in the patch release, most teams should target the upgrade within 2–4 weeks rather than waiting for the full window.

Sources

  1. GitHub Security Advisory GHSA-j4cx-787j-xjqg
  2. @fastify/jwt npm package
  3. fastify-jwt GitHub releases
  4. fastify-jwt GitHub repository
  5. Socket.dev @fastify/jwt security analysis
  6. FIRST EPSS
  7. CISA KEV catalog
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.