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.
4 steps from start to impact.
Obtain valid low-privilege JWT
- Target application uses
@fastify/jwt< 10.2.2 - Application exposes account registration or the attacker has compromised a low-privilege account
- Applications behind SSO / IdP federation may not issue JWTs from
@fastify/jwtdirectly - Rate limiting on auth endpoints is standard
Identify routes using per-request key verification
- Application uses
request.jwtVerify({ verify: { key } })on at least one route
- 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
Present global-secret JWT on key-protected route
@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.- Vulnerable version of
@fastify/jwtin use - Route handler does not perform secondary, out-of-band key verification
- 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
kid or iss claims versus expected values may flag anomalous tokens. No CVE-specific scanner signatures observed as of 2026-08-15.Access unauthorized resources
- Route handler trusts the JWT verification result without additional authorization checks
- 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
The supporting signals.
| In-the-wild exploitation | No known exploitation as of 2026-08-15. Not listed on CISA KEV. |
|---|---|
| Proof-of-concept | No 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. |
| EPSS | Not yet scored (advisory published 2026-08-14). Expected to be low (<5th percentile) given library-level bug requiring specific usage pattern. |
| KEV status | Not listed. No CISA KEV entry as of 2026-08-15. |
| CVSS vector | CVSS: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 version | 10.2.2 (released 2026-08-14 via npm) |
| Scanning / exposure | Not 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 date | 2026-08-14 (GHSA-j4cx-787j-xjqg published alongside patch) |
| Reporter | Matteo Collina (@mcollina), Fastify core maintainer — coordinated self-disclosure. |
noisgate verdict.
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.
Why this verdict
- Narrow affected pattern: The bug only fires when an application uses
request.jwtVerify({ verify: { key } })orreply.jwtSign(payload, { key }). Applications using only the global secret (likely the majority of@fastify/jwtconsumers) 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/jwtis 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.
What to do — in priority order.
- 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 usefastify.jwt.verify()instead ofrequest.jwtVerify()eliminates the vulnerability without upgrading. Deploy within 30 days per noisgate mitigation SLA for HIGH. - Add claims-based authorization checks independent of signature verification — Validate
iss,aud,kid, or customtenant_idclaims 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. - Run
npm auditand SCA scans to identify affected services — Usenpm 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. - Upgrade to
@fastify/jwt10.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.
- 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.
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.
#!/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 2If you remember one thing.
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
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.