A toll booth that only works when the highway sign is right-side-up — flip one config flag and cars blow through unchecked
CVE-2026-112362 is an authentication-bypass flaw in @fastify/express versions 4.0.5 through 4.0.8, the bridge plugin that lets teams run Express middleware inside a Fastify server. When Fastify's ignoreDuplicateSlashes router option is enabled (it defaults to false), the plugin's path-matching logic normalizes duplicate slashes in a way that also collapses the :// separator in absolute-form HTTP request targets (e.g., GET http://host//admin/secret). Fastify's router resolves the request to the scoped route and executes the handler, but the Express middleware layer — which was supposed to enforce auth, authz, or rate-limiting on that path — never sees the match and silently steps aside. An unauthenticated remote attacker can reach any protected handler behind Express middleware with a single crafted HTTP request.
The vendor scored this CVSS 9.1 CRITICAL, treating Attack Complexity as Low. That overstates the real-world exposure. The ignoreDuplicateSlashes option defaults to false; you must opt in explicitly. @fastify/express itself is a transitional bridge library (~103K weekly npm downloads) — most Fastify deployments don't load it at all, and of those that do, only a minority flip ignoreDuplicateSlashes. The CVSS vector's AC:L does not reflect that non-default prerequisite. That said, when the stars align — the flag is on, Express middleware guards the route, and the service is internet-facing — the bypass is trivially exploitable with a one-liner curl, and the impact is full auth bypass with no user interaction. This is a genuine HIGH-severity issue for affected deployments, but calling it CRITICAL inflates priority across the fleet.
4 steps from start to impact.
Identify a @fastify/express service with ignoreDuplicateSlashes
ignoreDuplicateSlashes: true set in the Fastify server factory. This is a non-default configuration. Reconnaissance typically involves probing for path-normalization behavior — sending requests with duplicate slashes and observing whether the server collapses them (200 vs 404 on //health vs /health).- Target runs @fastify/express ≥4.0.5 <4.0.9
- Fastify is configured with
ignoreDuplicateSlashes: true - Service is network-reachable
- ignoreDuplicateSlashes defaults to false — most deployments never enable it
- @fastify/express is a niche migration-bridge library; many Fastify apps use native middleware or @fastify/middie instead
- No banner or header reveals the Fastify config flag
X-Powered-By is commonly stripped.Locate an Express-middleware-protected scoped route
/admin or /api/v1/internal. The auth check is registered via app.use('/admin', authMiddleware) in the Express compatibility layer. The attacker tries common admin paths or uses directory brute-forcing tools like ffuf or feroxbuster.- A scoped route exists that is protected by Express middleware (not a Fastify preHandler hook)
- The auth/authz logic lives exclusively in that Express middleware layer
- Teams that followed Fastify best practices use
preHandlerhooks for auth, which are unaffected - Applications may have defense-in-depth (e.g., WAF path rules, Cloudflare Access, API gateway auth) independent of the Express middleware
Craft an absolute-form HTTP request with duplicate slashes
GET http://target.example.com//admin/dashboard HTTP/1.1. Fastify's duplicate-slash normalizer collapses //admin to /admin and routes to the handler. Simultaneously, the :// in the scheme gets mangled in the Express layer's path extraction, so the Express middleware mounted on /admin does not match and is skipped entirely. Tools: plain curl, httpie, or any HTTP client that allows raw request-line control.- Attacker can send HTTP/1.1 requests with absolute-form targets to the server
- No upstream proxy rewrites or strips absolute-form request lines before they reach Fastify
- Some reverse proxies (nginx, HAProxy) normalize or reject absolute-form request targets before they reach the application, breaking the attack chain
- HTTP/2 frontends decompose the request into pseudo-headers, eliminating the absolute-form vector entirely
- CDN/edge proxies (Cloudflare, AWS ALB) may normalize or reject non-standard request forms
GET http:// in the request line of access logs is a low-false-positive indicator.Access protected handler without authentication
- The handler behind the middleware returns sensitive data or allows state-changing operations
- If the handler itself performs secondary auth checks (e.g., verifying a JWT in the request body), the bypass is mitigated
- Application-level audit logging may still fire, creating forensic evidence
npm install @fastify/[email protected] or update your lockfile and redeploy. Per the noisgate mitigation SLA for HIGH severity, deploy within 30 days. Given the trivial exploitability when preconditions are met and same-day patch availability, aim for days, not weeks.ignoreDuplicateSlashes to false (the default) in your Fastify server factory options. This completely eliminates the attack vector because the path normalization that collapses the scheme separator no longer runs. Test your application for any routes that depend on duplicate-slash tolerance. Deploy within the 30-day noisgate mitigation SLA for HIGH.app.use('/path', authMw)) to Fastify's preHandler hook, which executes *after* the router resolves the request and is unaffected by this normalization gap. This is the vendor-recommended workaround and also hardens against the related CVE-2026-33808. Even after patching, this is better architectural hygiene. Deploy within the 30-day mitigation window.curl --request-target 'http://yourhost//admin' http://yourhost/ and confirming a 400 or rewritten path in Fastify's access log. This is defense-in-depth, not a standalone fix.- Network-level firewalling / IP allowlisting — The attack uses a standard HTTP request on the same port and path the service legitimately serves. Firewall rules cannot distinguish the malicious absolute-form target from normal traffic.
- WAF generic SQL injection / XSS rulesets — The payload is a well-formed URL with no injection characters. Standard OWASP CRS rules do not inspect the request-line form (absolute vs. relative).
- Rate limiting on the unprotected path — The entire point of the bypass is that the rate-limiting middleware is skipped. Adding rate limiting in the same Express middleware layer does not help; it must be in a Fastify hook or at the reverse proxy.
The supporting signals.
| In-the-Wild Exploitation | No evidence. Not listed in CISA KEV. No campaigns or threat-actor usage reported as of 2026-10-27. Disclosure is same-day as this assessment. |
|---|---|
| Proof-of-Concept Availability | No public PoC. Not indexed on pocindex.io, no GitHub repos named after CVE-2026-112362, no ExploitDB or Metasploit module. However, the exploit is conceptually trivial — a single curl command with an absolute-form URL — so weaponization requires minutes, not days. |
| EPSS Score | Not yet scored — CVE was disclosed 2026-10-27 (today). FIRST EPSS model typically ingests new CVEs within 24–72 hours. Expect a low-to-moderate EPSS given the non-default config prerequisite. |
| CISA KEV Status | Not listed. No due date imposed. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N — vendor-assigned 9.1. The AC:L is debatable; the non-default ignoreDuplicateSlashes flag is a prerequisite the vector does not capture. With AC:H the score drops to 7.4. |
| Affected Versions | @fastify/express ≥4.0.5 <4.0.9 (npm). Versions <4.0.5 are affected by the *separate* CVE-2026-33808 (GHSA-6hw5-45gm-fj88), a related but distinct normalization bypass. |
| Fixed Version | @fastify/express 4.0.9 — released 2026-10-27. No distro backports apply; this is a pure npm package. |
| Package Footprint | ~103K weekly npm downloads. This is a migration-bridge library, not core Fastify. For comparison, fastify core sees ~5M+ weekly downloads. The vulnerable population is a small fraction of the Node.js ecosystem. |
| Disclosure Timeline | Reserved 2026-10-19 by OpenJS Foundation (CNA). Published and patched simultaneously on 2026-10-27. Coordinated disclosure with patch available at announcement. |
| Researcher / CNA | Reported through the OpenJS Foundation security process. GHSA ID: GHSA-6h42-57hq-9746. No individual researcher publicly credited at time of disclosure. |
Sources.
Why this verdict
- Non-default configuration gate: The
ignoreDuplicateSlashesoption defaults tofalse. The attack chain is dead unless the deployer explicitly opts in. The CVSS vector's AC:L does not account for this; recalculating with AC:H gives 7.4. This is the single largest downward adjustment from the vendor's 9.1. - Niche library, narrow installed base: @fastify/express has ~103K weekly npm downloads — a bridge library for Express-to-Fastify migrations. Most Fastify deployments (5M+ core downloads) never load this plugin. The intersection of 'uses @fastify/express' AND 'enables ignoreDuplicateSlashes' AND 'relies on Express middleware for auth' is a small population.
- Upstream proxy normalization breaks the chain: The absolute-form request target required for exploitation is non-standard. nginx (the most common Node.js reverse proxy) normalizes request targets before forwarding. AWS ALB, Cloudflare, and HTTP/2 frontends similarly decompose or reject absolute-form lines. Many production deployments are shielded by infrastructure they already have.
- No PoC, no exploitation, day-zero disclosure: Patch shipped simultaneously with advisory. No weaponized exploit exists. While the exploit is conceptually simple, the timing gives defenders a window before any scanning begins.
- Role multiplier: @fastify/express is an application-layer library, not infrastructure. It does not canonically occupy a high-value role (not an IdP, hypervisor, CI/CD system, CA, or network edge). The worst plausible high-value scenario is an API gateway or admin service built on Fastify+Express bridge with the non-default flag — this is realistic but not the dominant deployment pattern. Blast radius is application-scoped (data in that service), not domain-wide or fleet-wide. This keeps the floor at HIGH but does not elevate to CRITICAL.
- Trivial exploitation when conditions met (floor enforcement): Despite the friction, when an app *does* meet all preconditions, exploitation requires only a
curlone-liner from an unauthenticated remote position, yielding complete auth bypass. This prevents downgrading below HIGH.
Why not higher?
The non-default ignoreDuplicateSlashes configuration requirement is a hard gate that the majority of @fastify/express deployments do not pass through. The package itself is a transitional bridge library (~103K weekly downloads vs. ~5M for Fastify core), not infrastructure that canonically sits in a high-value role. The blast radius of a successful exploit is scoped to a single application instance — there is no domain takeover, fleet compromise, or supply-chain pivot inherent to this vulnerability. Without evidence of active exploitation or a public PoC driving mass scanning, CRITICAL is not warranted.
Why not lower?
When the preconditions are met, exploitation is trivially achievable by an unauthenticated remote attacker with a single HTTP request — no tooling, no credentials, no user interaction. The impact is complete authentication and authorization bypass on scoped routes, which can expose admin panels and sensitive data. The attack is network-reachable (AV:N), and some production deployments serving real user data will have this exact configuration. A MEDIUM rating would understate the risk for those environments and could lead to deprioritization that leaves exploitable services exposed.
Crowdsourced verification payload.
Run this script on any machine with node (≥16) and network access to your npm registry or local node_modules. It checks the installed version of @fastify/express in the current project directory. No special privileges needed. Usage: chmod +x check_cve_2026_112362.sh && ./check_cve_2026_112362.sh /path/to/your/project
#!/usr/bin/env bash
# check_cve_2026_112362.sh — Detect CVE-2026-112362 in @fastify/express
# Checks installed package version and Fastify config for ignoreDuplicateSlashes
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
PROJECT_DIR="${1:-.}"
PKG_NAME="@fastify/express"
FIXED_VERSION="4.0.9"
MIN_VULN="4.0.5"
if [ ! -d "$PROJECT_DIR/node_modules" ]; then
echo "UNKNOWN — node_modules not found in $PROJECT_DIR. Run 'npm install' first or pass the correct project path."
exit 2
fi
PKG_JSON="$PROJECT_DIR/node_modules/@fastify/express/package.json"
if [ ! -f "$PKG_JSON" ]; then
echo "UNKNOWN — $PKG_NAME is not installed in $PROJECT_DIR."
exit 2
fi
INSTALLED=$(node -e "console.log(require('$PKG_JSON').version)" 2>/dev/null)
if [ -z "$INSTALLED" ]; then
echo "UNKNOWN — could not read version from $PKG_JSON."
exit 2
fi
echo "Installed $PKG_NAME version: $INSTALLED"
# Compare versions using node's semver-compatible comparison
IS_VULN=$(node -e "
const v = '$INSTALLED'.split('.').map(Number);
const min = '$MIN_VULN'.split('.').map(Number);
const fix = '$FIXED_VERSION'.split('.').map(Number);
function cmp(a, b) {
for (let i = 0; i < 3; i++) {
if (a[i] < b[i]) return -1;
if (a[i] > b[i]) return 1;
}
return 0;
}
const vuln = cmp(v, min) >= 0 && cmp(v, fix) < 0;
console.log(vuln ? 'YES' : 'NO');
" 2>/dev/null)
if [ "$IS_VULN" = "YES" ]; then
# Check for ignoreDuplicateSlashes in source files
echo ""
echo "Checking for ignoreDuplicateSlashes usage..."
MATCHES=$(grep -rl 'ignoreDuplicateSlashes' "$PROJECT_DIR/"*.js "$PROJECT_DIR/src/" "$PROJECT_DIR/lib/" 2>/dev/null || true)
if [ -n "$MATCHES" ]; then
echo "WARNING: ignoreDuplicateSlashes found in:"
echo "$MATCHES"
echo ""
echo "VULNERABLE — $PKG_NAME $INSTALLED is in the affected range ($MIN_VULN – <$FIXED_VERSION) AND ignoreDuplicateSlashes is referenced in source. CVE-2026-112362 applies."
exit 1
else
echo "ignoreDuplicateSlashes not found in project source (checked .js, src/, lib/)."
echo ""
echo "VULNERABLE (version) — $PKG_NAME $INSTALLED is in the affected range ($MIN_VULN – <$FIXED_VERSION), but ignoreDuplicateSlashes was not detected in source. Verify manually that this option is not set at runtime. Upgrade to $FIXED_VERSION+ regardless."
exit 1
fi
else
echo ""
echo "PATCHED — $PKG_NAME $INSTALLED is outside the vulnerable range ($MIN_VULN – <$FIXED_VERSION)."
exit 0
fi