← Back to Feed CACHED · 2026-08-18 20:38:14 · CACHE_KEY CVE-2026-18504
CVE-2026-18504 · CWE-20 · Disclosed 2026-08-18

fastify vulnerable to schema validation bypass via root primitive coercion mismatch

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

The bouncer checks your fake ID, says it's legit, then hands your real one to the bartender anyway

CVE-2026-18504 affects Fastify versions prior to 5.12.1. When a route's body schema defines a root-level primitive type (e.g., { type: 'integer' }) and Ajv's default type coercion is enabled, Fastify validates the *coerced* value but exposes the *original, uncoerced* value to the route handler. A JSON body of "10" coerces to integer 10, passes the schema check, but request.body remains the string "10". Object and array body schemas are not affected because their members are coerced in-place. Only root-level primitive body schemas trigger the mismatch.

The vendor's MEDIUM / 5.4 rating is directionally correct and arguably generous. The attack requires authentication (PR:L), the affected surface is narrow (root primitive schemas are uncommon in production REST APIs — the vast majority accept JSON objects), and the impact is limited to low confidentiality and low integrity (C:L/I:L/A:N). There is no path to code execution, privilege escalation, or denial of service. The vulnerability is real but highly situational — exploitation requires the application to make a downstream security decision (e.g., an authorization boundary, a rate-limit check, a financial calculation) that depends on the *type* of the validated root primitive, which is an uncommon pattern.

"Narrow type-confusion bug in Fastify root primitives; most APIs use objects and are unaffected."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify a root-primitive endpoint

The attacker, who already holds valid credentials (PR:L), surveys the API surface for routes that accept a bare primitive body schema — e.g., POST /transfer expecting { type: 'integer' } for an amount field. This is discoverable through OpenAPI/Swagger docs that Fastify commonly auto-generates, or via trial and error against endpoints that reject object payloads.
Conditions required:
  • Valid authenticated session or API key
  • Target application uses Fastify < 5.12.1
  • At least one route defines a root-level primitive body schema
Where this breaks in practice:
  • Most production APIs use object body schemas ({ type: 'object', properties: {...} }), not bare primitives
  • Developers following REST conventions will wrap primitives in objects
STEP 02

Send a string where an integer is expected

The attacker sends a POST request with a JSON string body that would coerce to the expected primitive type — e.g., "10" instead of 10. Fastify's Ajv validator coerces "10" to 10, validates it against the integer schema, and passes. However, request.body is set to the original string "10" rather than the coerced integer.
Conditions required:
  • The value must be coercible to the target type under Ajv rules
Where this breaks in practice:
  • If the handler performs its own type check or cast, the mismatch is neutralized
  • Many frameworks and ORMs downstream will reject or re-cast the wrong type
Detection/coverage: WAF rules checking for type mismatches in JSON bodies are uncommon; this is not a signature-detectable attack pattern.
STEP 03

Exploit type-dependent business logic

The handler processes request.body as if it were a validated integer, but it is a string. This can bypass security checks that rely on numeric comparisons — e.g., if (request.body > 1000) throw 'limit exceeded' succeeds because JavaScript string comparison "10" > 1000 is false (string-to-number coercion in JS comparison operators makes this unreliable as a bypass in many cases, but edge cases exist with === strict equality checks, typeof guards, or downstream systems that handle strings differently from integers).
Conditions required:
  • Application must make a security-relevant decision based on the type or typed comparison of request.body
  • No downstream re-validation or type casting
Where this breaks in practice:
  • JavaScript's loose comparison operators often re-coerce the string back to a number, neutralizing the mismatch
  • Typed languages in downstream microservices (Go, Java, Rust) will reject the wrong type at deserialization
  • Most security-critical operations use parameterized queries or typed ORMs that enforce types independently
Detection/coverage: Application-level logging that records typeof request.body would reveal the mismatch. No CVE-specific scanner signatures exist as of 2026-08-19.
STEP 04

Achieve limited C/I impact

If the type confusion bypasses a business-logic guard, the attacker may read data they shouldn't (C:L) or modify records in unintended ways (I:L). The scope is unchanged (S:U) — there is no breakout to other components or privilege escalation to system-level access. No availability impact is documented.
Conditions required:
  • Successful bypass of a type-dependent security check in step 3
Where this breaks in practice:
  • Blast radius is limited to the specific business function behind the affected endpoint
  • No lateral movement, no RCE, no persistence
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. No reports of active exploitation. Not listed in CISA KEV.
Proof of conceptNot publicly available. The GHSA advisory does not include a PoC. The concept is trivially reproducible by sending a JSON string to a root-primitive endpoint, but weaponization requires a target-specific business-logic bypass.
EPSS scoreNot yet scored — CVE is RESERVED in NVD as of 2026-08-19; EPSS data not available. Expected to be low (<5th percentile) given the authentication requirement and narrow attack surface.
KEV statusNot listed. No CISA KEV entry.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N — Network-accessible but requires authentication. Low complexity, no user interaction. Limited confidentiality and integrity impact, no availability impact, unchanged scope.
Affected versionsfastify < 5.12.1 (npm). Only versions with Ajv type coercion enabled and root-level primitive body schemas are exploitable.
Fixed versionfastify 5.12.1 (released 2026-08-18)
Exposure dataFastify has ~9M weekly downloads on npm. However, the subset of applications using root-level primitive body schemas is estimated to be very small — likely <5% of deployments.
Disclosure date2026-08-18 (coordinated disclosure via GitHub Security Advisory)
Reporter / creditsReported by velgusgus599, fix by mcollina, reviewed by UlisesGascon
04 · The Call

noisgate verdict.

Final Verdict
= UNCHANGED to MEDIUM (4.8/10)

The single most decisive factor is the extremely narrow attack surface: only root-level primitive body schemas (not objects or arrays) are affected, which represents a small fraction of real-world Fastify route definitions. Combined with the authentication requirement and the absence of any code-execution or privilege-escalation path, the blast radius even on high-value deployments remains limited to business-logic-level type confusion on individual endpoints.

HIGH Vulnerability mechanics and affected versions
MEDIUM Real-world exploitability assessment (no EPSS data, no known exploitation)
LOW Installed-base fraction using root primitive schemas (estimated <5%)

Why this verdict

  • Narrow schema surface: Only root-level primitive body schemas are affected. The overwhelming majority of Fastify APIs use object schemas ({ type: 'object' }), which coerce members in-place and are completely unaffected. This drastically limits the reachable population.
  • Authentication required (PR:L): The attacker must hold valid credentials or an API key. This is not an unauthenticated drive-by — it requires a prior relationship with the application.
  • No escalation path: Impact caps at C:L/I:L/A:N with unchanged scope. There is no route to RCE, SSRF, privilege escalation, or system compromise. The worst case is a business-logic bypass on a single endpoint.
  • Role multiplier: Fastify is a general-purpose Node.js web framework. In a *typical role* (API backend, microservice), exploitation yields a minor business-logic bypass. In a *high-value role* (API gateway, auth service, CI/CD frontend), the type confusion could theoretically affect an authorization decision — but the chain still requires a root-primitive schema AND a type-dependent security check, which is architecturally uncommon in identity or gateway services. The blast radius even in high-value roles does not reach domain/fleet/supply-chain scale. No floor override triggered.
  • JavaScript coercion semantics partially self-heal: Many downstream comparison operators in JavaScript re-coerce strings to numbers, reducing the practical impact of the type mismatch in common code patterns.

Why not higher?

There is no path to remote code execution, privilege escalation, or denial of service. The scope is unchanged (S:U), and impact is capped at low confidentiality and low integrity. The authentication requirement removes the unauthenticated-mass-exploitation scenario. Even in high-value Fastify deployments (API gateways, auth services), the specific preconditions — root primitive schema + type-dependent security decision — are architecturally uncommon enough that the blast radius does not reach fleet or domain scale.

Why not lower?

The vulnerability is real, trivially triggerable once preconditions are met, and affects a framework with ~9M weekly npm downloads. Type coercion is enabled by default in Fastify, so the misconfiguration is opt-out rather than opt-in. While the affected surface is narrow, a developer who does use a root primitive schema for a security-critical endpoint (e.g., a financial amount or a permission level) would have a false sense of safety from validation that is silently broken.

05 · Compensating Control

What to do — in priority order.

  1. Wrap root primitives in object schemas — Change schema: { body: { type: 'integer' } } to schema: { body: { type: 'object', properties: { value: { type: 'integer' } } } }. Object schemas coerce members in-place and are not affected by this bug. This is a code change, not an infrastructure control — prioritize it within your 365-day noisgate remediation SLA for MEDIUM findings.
  2. Add explicit type checks in security-critical handlers — Insert if (typeof request.body !== 'number') return reply.code(400).send(...) at the top of any handler that depends on a root primitive type for a security decision. This neutralizes the mismatch regardless of Fastify version.
  3. Disable Ajv type coercion for body schemas — Configure the Ajv validator compiler with coerceTypes: false for body validation. This eliminates the coercion mismatch entirely but may break existing routes that rely on coercion. Test thoroughly before deploying.
  4. Upgrade to fastify ≥ 5.12.1 — The definitive fix. The patched version ensures request.body receives the coerced value, matching what was validated. Schedule within your standard dependency update cycle.
What doesn't work
  • WAF rules — This is not a payload-signature attack. The malicious input is a valid JSON string ("10") that differs from the expected type. No WAF can distinguish this from legitimate usage without application-level schema awareness.
  • Rate limiting — The attack does not involve volume; a single request can trigger the bypass.
  • Content-Type header validation — Unlike CVE-2026-33806 and CVE-2026-25223, this vulnerability does not involve Content-Type manipulation. Hardening Content-Type parsing does not help.
06 · Verification

Crowdsourced verification payload.

Run on the target host or any machine with node and npm available. Execute: bash check_cve_2026_18504.sh /path/to/your/project. No special privileges required — it reads package.json and node_modules.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_18504.sh — Detect fastify < 5.12.1 (CVE-2026-18504)
# Usage: bash check_cve_2026_18504.sh [/path/to/project]
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -euo pipefail

PROJECT_DIR="${1:-.}"

if ! command -v node &>/dev/null; then
  echo "UNKNOWN — node not found in PATH"
  exit 2
fi

# Try npm list first (handles hoisted deps)
if command -v npm &>/dev/null; then
  FASTIFY_VER=$(cd "$PROJECT_DIR" && npm ls fastify --json 2>/dev/null | node -e "
    const j=require('fs').readFileSync('/dev/stdin','utf8');
    try { const d=JSON.parse(j); const v=d.dependencies?.fastify?.version; if(v) console.log(v); else process.exit(1); } catch(e){ process.exit(1); }
  " 2>/dev/null) || true
fi

# Fallback: read from node_modules
if [ -z "${FASTIFY_VER:-}" ]; then
  PKG="$PROJECT_DIR/node_modules/fastify/package.json"
  if [ -f "$PKG" ]; then
    FASTIFY_VER=$(node -e "console.log(require('$PKG').version)" 2>/dev/null) || true
  fi
fi

if [ -z "${FASTIFY_VER:-}" ]; then
  echo "UNKNOWN — fastify not found in $PROJECT_DIR"
  exit 2
fi

echo "Detected fastify version: $FASTIFY_VER"

# Compare versions: vulnerable if < 5.12.1
RESULT=$(node -e "
  const semver = (v) => v.split('.').map(Number);
  const [ma,mi,pa] = semver('$FASTIFY_VER');
  const [fa,fi,fp] = semver('5.12.1');
  if (ma < 5) { console.log('PATCHED'); process.exit(0); } // v4.x not affected by this specific advisory
  if (ma > fa || (ma===fa && mi > fi) || (ma===fa && mi===fi && pa >= fp)) {
    console.log('PATCHED');
  } else {
    console.log('VULNERABLE');
  }
")

echo "$RESULT"
if [ "$RESULT" = "VULNERABLE" ]; then exit 1; fi
if [ "$RESULT" = "PATCHED" ]; then exit 0; fi
exit 2
07 · Bottom Line

If you remember one thing.

TL;DR
This is a MEDIUM finding with a narrow real-world blast radius. There is no mitigation SLA for MEDIUM severity under the noisgate framework — go straight to the 365-day noisgate remediation SLA. Practically, if you run Fastify 5.x in production, add [email protected] to your next scheduled dependency update cycle. Before that update lands, audit your codebase for any route using a root-level primitive body schema (e.g., { type: 'integer' } or { type: 'string' } at the body root) — if you find none, you are not exploitable regardless of version. If you do find affected routes, either wrap the primitive in an object schema or add an explicit typeof guard in the handler as an interim fix. There is no active exploitation and no PoC in the wild, so this does not warrant emergency patching.

Sources

  1. GitHub Security Advisory GHSA-w2qp-rph6-63g4
  2. Fastify releases (GitHub)
  3. Fastify Validation and Serialization docs
  4. Ajv type coercion rules
  5. GitLab Advisory Database — fastify
  6. Fastify npm trends and download stats
  7. FIRST EPSS FAQ
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.