← Back to Feed CACHED · 2026-07-01 16:56:08 · CACHE_KEY CVE-2026-14198
CVE-2026-14198 · CWE-436 · Disclosed 2026-01-19

@fastify/middie vulnerable to authorization bypass via encoded slash in path parameter values

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

The doorman checks IDs at the front door, but there's a side door with a different sign on it

@fastify/middie is the Express-compatibility shim that lets Fastify apps run classic app.use('/admin', authMiddleware) style middleware. The bug: middie matches the middleware's path prefix against req.url in its raw, undecoded form, while Fastify's underlying router decodes percent-encoded characters before dispatching to the route handler. An attacker who requests /%61dmin/users sees middie's regex fail to match /admin, so the auth middleware is skipped — but Fastify's router happily decodes and routes to the protected /admin/users handler. Affected versions are @fastify/middie ≤ 9.0.3; patched in 9.1.0. The pattern also extends beyond the letter-encoding trick: encoded slashes (%2F) inside path parameter values can produce the same canonicalization drift.

Vendor-labeled CRITICAL 9.1 (AV:N/AC:L/PR:N/UI:N) is overstated for a library-level flaw whose exploitability is entirely gated on a specific usage pattern. The upstream GHSA-cxrg-g7r8-w69p actually scored this 8.4 HIGH with AC:H and PR:L, which is closer to reality. When apps *do* use middie for path-scoped auth, this is a full unauthenticated bypass and deserves HIGH. When they use Fastify hooks (onRequest, preHandler) — the idiomatic Fastify pattern — middie is not in the auth path and the CVE is inert.

"Real bypass, real impact — but only for apps that use middie for path-scoped auth. Global hooks and reverse-proxy normalization defang most deployments."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify a Fastify app using middie for path-scoped auth

Attacker fingerprints the target by observing Server: headers, response idioms, or 404 payloads characteristic of Fastify. They probe protected routes (/admin, /internal, /api/private) and confirm a 401/403 response, indicating middleware-enforced auth. Tooling: httpx, nuclei with Fastify fingerprint templates, wappalyzer.
Conditions required:
  • Target exposes a Fastify-based API
  • Protected paths are discoverable via wordlist or JS bundle recon
Where this breaks in practice:
  • Fastify best-practice guidance directs authors to hooks (onRequest/preHandler), not middie
  • Many teams use middie only for legacy Express middleware like helmet/morgan, not auth
Detection/coverage: Standard web recon; no vuln-specific scanner signature at this stage
STEP 02

Craft encoded-path variant of the protected route

Attacker substitutes one or more ASCII characters in the protected prefix with their percent-encoded form (e.g., /admin/%61dmin, /%41dmin, /adm%69n). Alternatively, they insert %2F inside path parameter values to shift the router's canonical view of the path. Tooling: Burp Suite Intruder with URL-encoding payload set, ffuf with -w encoding-bypass.txt.
Conditions required:
  • Path-scoped middleware registered on the exact prefix
  • No upstream normalizer strips or rejects percent-encoding
Where this breaks in practice:
  • nginx/HAProxy/Cloudflare reverse proxies often decode or normalize %XX sequences before forwarding, defeating the bypass at the edge
  • WAF path-normalization rules (ModSecurity CRS 942, Cloudflare Managed Ruleset) often catch encoded-slash tricks
Detection/coverage: WAFs with URL-normalization rules will flag; raw Fastify + no WAF will not
STEP 03

Bypass middie regex, hit protected handler

The crafted request reaches Fastify. middie's path-to-regexp compare against raw req.url fails to match the prefix, so it calls next() without invoking the auth middleware. Fastify's router then percent-decodes the URL and dispatches to the protected handler, which now runs with no authenticated principal on the request context.
Conditions required:
  • Handler does not itself re-verify identity (common — it trusts the middleware)
  • No secondary hook-based auth check
Where this breaks in practice:
  • Defense-in-depth handlers that call request.user/request.session and 401 on missing identity break the chain
  • GraphQL/tRPC-style handlers that resolve auth per-resolver are unaffected
Detection/coverage: Application logs will show a 200 to /%61dmin/... from an unauthenticated source — trivially greppable if anyone looks
STEP 04

Enumerate and exfiltrate via bypassed endpoint

With the auth gate skipped, the attacker exercises whatever functionality the protected route exposes — admin API calls, tenant data reads, config mutations. Tooling: curl loops, kiterunner for API path discovery once bypass is confirmed.
Conditions required:
  • Protected endpoint returns useful data or performs privileged action
Where this breaks in practice:
  • Handler-side RBAC (role checks against JWT claims) inside the route body would catch this
  • Rate limits and WAF anomaly scores flag high-volume enumeration
Detection/coverage: SIEM anomaly on 2xx responses to admin paths from non-authenticated sessions
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNo confirmed campaigns as of 2026-07-02. Not on CISA KEV.
Proof-of-conceptPublic PoC in the GHSA writeup by researcher rootxharsh; trivial one-liner (curl https://target/%61dmin/users). No mass-exploit tooling published.
EPSSCurrently low (~0.4–1.2 percentile band typical for library-level auth bypasses without wormable primitive)
KEV statusNot listed
CVSS vector (vendor)CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N → 9.1 CRITICAL. Upstream GHSA-cxrg-g7r8-w69p rescored it 8.4 HIGH with AC:H/PR:L/S:C, reflecting the usage-pattern gate.
Affected versions@fastify/middie ≤ 9.0.3
Fixed version9.1.0 — normalizes req.url before regex match; also see 9.2.0 for hardening of duplicate-slash handling (GHSA-v9ww-2j6r-98q6)
Exposure telemetrymiddie is a *plugin*, not a network service — Shodan/Censys/GreyNoise cannot measure it directly. npm weekly downloads run in the low millions but only a fraction of dependents use it for auth vs. Express-legacy middleware.
Disclosure date2026-01-19 (GHSA); CVE assignment CVE-2026-14198
Reporterrootxharsh; coordinated via OpenJS Foundation CNA (mcollina)
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to HIGH (8.2/10)

This is a genuine unauthenticated auth bypass in a widely-used Node.js library, but exploitability is fully gated on a specific and non-canonical usage pattern (path-scoped middleware via middie used for auth, rather than idiomatic Fastify hooks). The single decisive factor for HIGH rather than CRITICAL is that the affected code path is opt-in developer behavior, not a default runtime primitive — the majority of Fastify apps do not route their auth through middie's path matcher.

HIGH Technical mechanism and patched version
MEDIUM Exposed-population estimate (no direct telemetry for library usage patterns)
LOW Active exploitation — no in-the-wild campaigns observed yet

Why this verdict

  • Usage-pattern gated: The bypass requires @fastify/middie to be the enforcement point for auth. Fastify's own docs steer authors toward onRequest/preHandler hooks; middie is primarily a compatibility layer. Only apps that violate the idiom are vulnerable — this narrows the exposed population well below the vendor's implicit AC:L/PR:N population.
  • Edge normalization mitigates for most enterprises: Reverse proxies (nginx, HAProxy, Cloudflare, AWS ALB) and mature WAFs decode or reject percent-encoded ASCII in path segments before Fastify sees the request. Enterprises running Fastify directly on 0.0.0.0:3000 without a proxy are the minority.
  • Role multiplier — application-tier library: middie sits inside line-of-business Node services (typical role). It is not a domain controller, hypervisor, IdP, PAM, backup, or kernel-mode agent. Worst-case blast radius per compromised app: tenant/app scope, not fleet or identity-plane. This sets the floor at HIGH, not CRITICAL.
  • Trivial to weaponize when applicable: Where the pattern exists, exploitation is a single curl with %XX-encoded characters — no auth, no chained conditions, no user interaction. That is what keeps this at HIGH rather than MEDIUM.
  • Not on KEV, no observed campaigns, but PoC is public and trivial. Mass scanning is plausible once someone bundles it into nuclei templates.

Why not higher?

CRITICAL is reserved for defaults-vulnerable primitives or high-value-role components (IdP, hypervisor, backup, kernel-mode agent). middie is a mid-tier application library, and the vulnerable code path is a non-idiomatic usage pattern rather than a default. Even where exploited, the blast radius stops at the compromised Node service — no fleet pivot, no identity-plane compromise.

Why not lower?

MEDIUM would understate an unauthenticated remote auth bypass with a trivially weaponized PoC and no user interaction. When the vulnerable pattern is present, this is a complete bypass — not a partial info leak. The presence of public PoC by a known researcher and the ease of nuclei-template weaponization keep it firmly at HIGH.

05 · Compensating Control

What to do — in priority order.

  1. Upgrade @fastify/middie to ≥ 9.1.0 (prefer 9.2.0) — The 9.1.0 patch normalizes req.url before the path-to-regexp match, aligning middie's view of the path with Fastify's router. 9.2.0 additionally hardens duplicate-slash handling from a sibling GHSA. Deploy within 30 days per the noisgate HIGH mitigation SLA.
  2. Reject percent-encoded ASCII letters at the edge — Add an nginx/HAProxy/WAF rule to return 400 for paths containing %XX sequences that decode to [A-Za-z] or %2F. This kills the bypass primitive regardless of middie version. Cloudflare Managed Rules and ModSecurity CRS 942 rulesets can do this out of the box.
  3. Move auth off middie onto Fastify hooks — Refactor app.use('/admin', authMiddleware) into fastify.addHook('onRequest', authFn) scoped via fastify.register with a prefix. Fastify hooks execute inside the router post-decoding and are not vulnerable to the drift. This is the idiomatic pattern and eliminates the class of bug, not just this CVE.
  4. Add handler-side identity assertion — Inside every protected handler, assert request.user (or equivalent context) is populated and fail closed if not. This is defense-in-depth against *any* middleware bypass, including future middie CVEs.
  5. Deploy SIEM rule for 2xx on admin paths without prior auth event — Correlate access-log 2xx responses on /admin/*, /internal/* prefixes against auth-log entries in the same session/JWT. Any 2xx without a corresponding successful auth is a bypass indicator.
What doesn't work
  • Rate limiting alone — the bypass is a single request, not a brute force; rate limits won't fire.
  • MFA / stronger passwords — the auth check is skipped entirely; credential strength is irrelevant.
  • mTLS between app and downstream services — only helps if the *bypassed* handler makes downstream calls; the initial bypass still succeeds.
  • Turning on ignoreDuplicateSlashes in Fastify router options — this is a separate sibling bug (GHSA-v9ww-2j6r-98q6) and actively *increases* attack surface pre-9.2.0.
  • IP allowlisting — helps only if attacker is external; does nothing for the insider or compromised-endpoint threat model.
06 · Verification

Crowdsourced verification payload.

Run on any host with node and npm installed against a checkout of the target Node.js application. Invoke with the project root as argument: ./check-middie.sh /path/to/app. No elevated privileges required — reads package-lock.json and node_modules only.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate: CVE-2026-14198 / GHSA-cxrg-g7r8-w69p checker for @fastify/middie
# Usage: ./check-middie.sh <path-to-node-project>
set -u

APP_DIR="${1:-.}"
FIXED_MAJOR=9
FIXED_MINOR=1
FIXED_PATCH=0

if [ ! -d "$APP_DIR" ]; then
  echo "UNKNOWN: directory $APP_DIR not found"
  exit 2
fi
cd "$APP_DIR" || { echo "UNKNOWN: cannot cd"; exit 2; }

# 1. Is middie even in the tree?
if [ ! -f "package-lock.json" ] && [ ! -f "pnpm-lock.yaml" ] && [ ! -f "yarn.lock" ]; then
  echo "UNKNOWN: no lockfile in $APP_DIR"
  exit 2
fi

VER=""
if [ -d "node_modules/@fastify/middie" ]; then
  VER=$(node -p "require('./node_modules/@fastify/middie/package.json').version" 2>/dev/null)
fi
if [ -z "$VER" ] && command -v npm >/dev/null 2>&1; then
  VER=$(npm ls @fastify/middie --depth=99 --json 2>/dev/null | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);const walk=n=>{if(!n)return;if(n.dependencies){for(const[k,v]of Object.entries(n.dependencies)){if(k==='@fastify/middie'&&v.version){console.log(v.version);process.exit(0)}walk(v)}}};walk(j)}catch(e){}})" )
fi

if [ -z "$VER" ]; then
  echo "PATCHED: @fastify/middie not present in dependency tree"
  exit 0
fi

# 2. semver compare
IFS='.' read -r MA MI PA <<< "${VER%%-*}"
if [ "$MA" -gt "$FIXED_MAJOR" ] \
  || { [ "$MA" -eq "$FIXED_MAJOR" ] && [ "$MI" -gt "$FIXED_MINOR" ]; } \
  || { [ "$MA" -eq "$FIXED_MAJOR" ] && [ "$MI" -eq "$FIXED_MINOR" ] && [ "$PA" -ge "$FIXED_PATCH" ]; }; then
  echo "PATCHED: @fastify/middie $VER >= 9.1.0"
  exit 0
fi

# 3. Check if middie is used for path-scoped auth (heuristic)
USAGE=$(grep -rEn "\\.use\\(['\"]/[^'\"]+['\"]" --include='*.js' --include='*.ts' --include='*.mjs' -- . 2>/dev/null | grep -v node_modules | head -5)

echo "VULNERABLE: @fastify/middie $VER (fix: >= 9.1.0)"
if [ -n "$USAGE" ]; then
  echo "  Path-scoped .use() calls detected — likely exploitable pattern:"
  echo "$USAGE" | sed 's/^/    /'
fi
exit 1
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: run the checker across your Node.js estate and inventory every service pulling @fastify/middie ≤ 9.0.3. For services using middie for path-scoped auth (app.use('/admin', authFn) pattern) — that is your HIGH cohort: upgrade to 9.1.0+ within 30 days per the noisgate mitigation SLA, and finish the version bump across the fleet within 180 days per the noisgate remediation SLA. If you can't upgrade in-window (vendored binary, frozen release train), deploy an edge WAF rule rejecting %XX-encoded ASCII letters and %2F in path segments — that shuts the primitive at the perimeter. Services that use middie only for legacy Express middleware like helmet/morgan and enforce auth via Fastify hooks can ride the standard remediation window without an emergency mitigation. Not KEV-listed and no observed campaigns, but the PoC is a one-line curl and will land in nuclei templates soon — do not sleep on this beyond the 30-day mitigation window.

Sources

  1. GHSA-cxrg-g7r8-w69p — Fastify Middie Middleware Path Bypass
  2. GHSA-8p85-9qpw-fwgw — Improper path normalization in middie
  3. GHSA-v9ww-2j6r-98q6 — middie bypass via ignoreDuplicateSlashes
  4. GHSA-6hw5-45gm-fj88 — Sibling advisory in @fastify/express
  5. GitLab Advisory Database — @fastify/middie path normalization
  6. OpenJS Foundation CNA — Security Advisories
  7. OSV — GHSA-8p85-9qpw-fwgw
  8. Fastify Hooks docs (idiomatic auth pattern)
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.