← Back to Feed CACHED · 2026-07-18 12:57:03 · CACHE_KEY CVE-2026-16158
CVE-2026-16158 · CWE-441 · Disclosed 2026-07-18

@fastify/reply-from vulnerable to cross-upstream request routing via URL cache key collision

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

A proxy that files two different envelopes into the same mailbox because it forgot to draw a line between the address and the room number

@fastify/reply-from builds an internal URL cache key by concatenating dest + source with no delimiter. That means the pair dest=http://127.0.0.1:3100 + source=1/private collides with dest=http://127.0.0.1:31001 + source=/private — both hash to the same string and resolve to the same upstream URL object. When an app uses getUpstream() to route between security-separated backends, an attacker who can influence either the path or the upstream selector can steer a request to the *wrong* upstream and read/write data it should not touch. Affected versions are 8.3.1 through 12.6.3; fixed in 12.6.4 (GHSA-v574-6498-x57v).

The vendor's HIGH / 8.7 rating is defensible on paper — it's a classic CWE-441 confused-deputy bug with S:C scope-change and C:H/I:H. But the CVSS math doesn't reflect how narrow the trigger is: the collision only fires when two upstream URLs happen to overlap as prefixes (a port-number or hostname where one is a lexical prefix of another) AND the app dispatches between them dynamically via getUpstream. Most real deployments proxy to one backend, or to backends with cleanly distinct hostnames. For the shops that *do* hit this pattern — multi-tenant gateways numbered sequentially — the score is accurate. For everyone else it's noise. We're calling it MEDIUM.

"Real bug, but the collision needs upstream URLs that share a prefix (e.g., :3100 vs :31001) — that's a rare architecture, not a fleet-wide fire."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Identify a vulnerable reply-from deployment

Attacker discovers a Fastify-based reverse proxy or API gateway using @fastify/reply-from <= 12.6.3. The proxy must be configured with dynamic upstream selection — either getUpstream() returning different backends per request, or multiple reply.from() calls to different hosts.
Conditions required:
  • Public or reachable Fastify gateway
  • Version <=12.6.3
  • Dynamic multi-upstream config
Where this breaks in practice:
  • Most reply-from installs proxy to a single backend and never call getUpstream
  • Fingerprinting the plugin externally is non-trivial — no distinct banner
Detection/coverage: SCA/SBOM scanners (Snyk, Dependabot, GHSA feed) flag the package version cleanly. No network signature.
STEP 02

Discover a colliding upstream URL pair

Attacker enumerates or infers the backend URL set. The collision requires two upstream URLs where the concatenation dest+source produces the same string as another dest'+source'. In practice this means adjacent port numbers (:3100 / :31001), sequential hostnames (api1/api10), or similar prefix overlap.
Conditions required:
  • Knowledge of at least two upstream URLs
  • URLs must be prefix-compatible
  • Ability to reason about path handling
Where this breaks in practice:
  • Requires source-code access, verbose errors, or lucky guessing
  • Well-designed backend naming (UUID, distinct domains, non-adjacent ports) makes collision impossible
Detection/coverage: No scanner detects the *configuration*; only the *package version* is flagged.
STEP 03

Craft path that poisons or hits the cached collision

Attacker sends a request whose source path, when concatenated with the chosen upstream's dest, matches the cache key of a different upstream+path pair. The library returns the *cached* upstream object from the first request, silently rerouting traffic to the wrong backend.
Conditions required:
  • Attacker-controlled path or upstream selector input
  • Cache warmed or warmable by attacker
Where this breaks in practice:
  • disableCache: true in plugin registration kills the whole class of attack
  • Path normalization middleware upstream (WAF, reverse-proxy) may reshape the request
Detection/coverage: Application-layer logs on the *wrong* backend will show unexpected paths — but only if you correlate cross-tier logs.
STEP 04

Read or write data belonging to another tenant / security zone

Once traffic is on the wrong upstream, the attacker either receives another tenant's data in the response or issues state-changing calls against a backend they should not reach. Impact depends entirely on whether the misrouted backend enforces its own authN/authZ or trusts the proxy.
Conditions required:
  • Misrouted backend does not re-authenticate the caller
  • Backends handle sensitive per-tenant or per-zone data
Where this breaks in practice:
  • Zero-trust backends that verify JWT/mTLS per request neutralize the impact
  • If both backends serve the same tenant scope, no security boundary is crossed
Detection/coverage: SIEM correlation between gateway and backend logs. Anomaly in per-tenant request volume.
03 · Intelligence Metadata

The supporting signals.

CVECVE-2026-16158 / GHSA-v574-6498-x57v
CWECWE-441 — Unintended Proxy or Intermediary (Confused Deputy)
Vendor CVSS8.7 HIGHCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:N (scope-changed, high conf/integ, no availability)
Affected versions@fastify/reply-from 8.3.1 → 12.6.3 (also downstream @fastify/http-proxy which wraps it)
Patched version12.6.4 — cache key now uses delimited concatenation
Disclosed2026-07-18 by *mcollina* (Matteo Collina, Fastify maintainer)
KEVNot listed on CISA KEV
EPSSNo EPSS score yet (published today) — expect <0.1% given library scope and preconditions
Exploitation in the wildNone observed. Same-day disclosure, no PoC circulating, no honeypot activity
Exposure telemetryGreyNoise/Shodan/Censys — no fingerprint for reply-from at the network layer; downloads via npm ~200k/week but active getUpstream users are a small subset
WorkaroundSet disableCache: true at plugin registration — closes the bug at the cost of per-request URL parsing
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.4/10)

Downgraded from HIGH 8.7 to MEDIUM 5.4 because the single most decisive factor is the extremely narrow architectural precondition — the bug only fires when two upstream URLs share a lexical prefix (e.g., :3100 vs :31001) AND the app dynamically routes between them via getUpstream(). In the majority of reply-from deployments there is one backend or backends with cleanly distinct hostnames, and the collision cannot be constructed at all.

HIGH Vulnerability mechanism and fix
HIGH Affected/patched version range
MEDIUM Real-world exposure population — depends on customer app configs, no telemetry
LOW Likelihood of public exploitation — no PoC beyond advisory example

Why this verdict

  • Preconditions are stacked and rare: dynamic getUpstream() routing + prefix-colliding upstream URLs + attacker-controlled path. Missing any one of the three and the bug is unreachable.
  • No availability impact and no code execution: worst case is cross-tenant data disclosure or state change on the wrong backend — bad, but not fleet-collapse bad.
  • Zero-trust backends neutralize impact: any backend that revalidates JWT/mTLS instead of trusting the proxy makes the misroute observable but not exploitable.
  • Role multiplier — API gateway / edge proxy: if reply-from is fronting a multi-tenant SaaS and terminates auth at the gateway, the chain succeeds with tenant-crossover impact. That is a real high-value role — but it's a small share of the install base and the URL-prefix precondition still gates it. Floor stays MEDIUM.
  • Role multiplier — single-backend proxy or dev tool: chain does not succeed; no collision possible. Majority of installs.
  • Same-day disclosure, no exploitation observed: patch is out, no active KEV pressure, no public weaponized PoC beyond the advisory's illustrative example.

Why not higher?

Not HIGH because the exploit chain requires a *specific* application architecture (dynamic multi-upstream routing between security-separated backends) AND *specific* upstream URL naming that produces the collision. That intersection is rare enough that most reply-from shops are not actually reachable. HIGH would imply this is a broad-blast-radius bug that patch teams should treat as a 30-day emergency — the facts don't support that.

Why not lower?

Not LOW because when the preconditions *are* met — multi-tenant gateway with sequentially-named backends — the impact is genuine cross-security-zone confused-deputy access, exactly the CWE-441 nightmare. It merits scheduled patching and a config audit, not silent burial in the backlog.

05 · Compensating Control

What to do — in priority order.

  1. Set disableCache: true at plugin registration — This is the vendor-endorsed mitigation. It disables the vulnerable URL cache entirely, at the cost of parsing the URL on every request (measurable but usually tolerable overhead). Ship this in a config-only change now; no mitigation SLA required at MEDIUM but treat it as low-risk hotfix within days if you route across security boundaries.
  2. Audit getUpstream() and multi-upstream configs for prefix-colliding URLs — Enumerate every distinct upstream URL your reply-from instances can route to. Confirm no pair produces the same dest+source concatenation for any reachable path. If you find a collision candidate, rename the host or port immediately (e.g., move :31001 to a non-adjacent value). Complete within the 365-day remediation window; sooner if you're multi-tenant.
  3. Enforce backend-side re-authentication — Every backend behind the proxy should validate the caller's identity (JWT signature + audience, mTLS client cert) instead of trusting the fact that traffic came from the gateway. This turns a successful misroute into a 401, neutralizing the confused-deputy chain entirely. This is standard zero-trust hygiene independent of the CVE.
  4. Upgrade @fastify/reply-from to 12.6.4+ and @fastify/http-proxy to the version pulling the fix — Definitive fix. Version bump only, no API change. Schedule inside the next dependency sweep — no mitigation SLA at MEDIUM, target within the 365-day remediation window (sooner is trivial since this is a semver-safe bump).
What doesn't work
  • WAF path-normalization rules — the collision is in the proxy's *cache key*, not in a URL-encoding trick, so no ModSecurity/Cloudflare rule will spot it.
  • Rate limiting — a single well-crafted request triggers the misroute; there is no burst to throttle.
  • TLS or mTLS at the edge — protects the wire, does nothing about server-side cache key logic.
  • Egress filtering — the misrouted traffic goes to a *legitimate* backend, just the wrong one; it will pass any allow-list.
06 · Verification

Crowdsourced verification payload.

Run on any host that has your Node.js app checked out or installed. Invoke as ./check-reply-from.sh /path/to/app (defaults to CWD). No elevated privileges required — this is a package-manifest audit. Exits 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate check: CVE-2026-16158 @fastify/reply-from URL cache key collision
# Vulnerable: 8.3.1 through 12.6.3   Patched: 12.6.4+
set -u
APP_DIR="${1:-$PWD}"
cd "$APP_DIR" 2>/dev/null || { echo "UNKNOWN: cannot cd $APP_DIR"; exit 2; }

vercmp() {
  # returns 0 if $1 < $2 (i.e. vulnerable), 1 otherwise
  printf '%s\n%s\n' "$1" "$2" | sort -V | head -1 | grep -qx "$1"
}

is_vuln() {
  local v="$1"
  # strip leading ^, ~, =, v, whitespace
  v="${v#[\^~=v]}"
  v="${v## }"
  [ -z "$v" ] && return 2
  # vulnerable if 8.3.1 <= v <= 12.6.3
  if vercmp "$v" "8.3.1"; then return 1; fi   # v < 8.3.1 -> not in range (older, unaffected here)
  if vercmp "12.6.3" "$v"; then
    # 12.6.3 < v -> patched
    return 1
  fi
  return 0
}

FOUND=0; VULN=0
while IFS= read -r -d '' pkg; do
  name=$(grep -oE '"name"[[:space:]]*:[[:space:]]*"@fastify/reply-from"' "$pkg" || true)
  [ -z "$name" ] && continue
  ver=$(grep -oE '"version"[[:space:]]*:[[:space:]]*"[^"]+"' "$pkg" | head -1 | sed -E 's/.*"([^"]+)"$/\1/')
  FOUND=1
  if is_vuln "$ver"; then
    echo "VULNERABLE: @fastify/reply-from@$ver at $pkg"
    VULN=1
  else
    echo "PATCHED:    @fastify/reply-from@$ver at $pkg"
  fi
done < <(find . -type d -name node_modules -prune -o -type d -print 2>/dev/null | xargs -I{} find {} -maxdepth 3 -name package.json -path '*/reply-from/*' -print0 2>/dev/null; find ./node_modules -name package.json -path '*@fastify/reply-from/*' -print0 2>/dev/null)

# Also check top-level package-lock / manifests
if [ -f package-lock.json ]; then
  grep -oE '"@fastify/reply-from"[[:space:]]*:[[:space:]]*\{[^}]*"version"[[:space:]]*:[[:space:]]*"[^"]+"' package-lock.json | \
    sed -E 's/.*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' | while read -r v; do
      FOUND=1
      if is_vuln "$v"; then echo "VULNERABLE: lockfile pins @fastify/reply-from@$v"; VULN=1; else echo "PATCHED: lockfile pins @fastify/reply-from@$v"; fi
    done
fi

if [ "$FOUND" -eq 0 ]; then echo "UNKNOWN: @fastify/reply-from not found under $APP_DIR"; exit 2; fi
exit "$VULN"
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: run the SBOM check across your Node.js estate and flag any app pulling @fastify/reply-from 8.3.1–12.6.3 (including transitive pulls via @fastify/http-proxy). For any hit that *also* uses getUpstream() or multiple reply.from() targets, ship disableCache: true as a config hotfix and audit the upstream URL list for prefix collisions — that is your real risk cohort. Verdict is MEDIUM, so per the noisgate mitigation SLA there is no mitigation deadline (MEDIUM has none — go straight to remediation), and per the noisgate remediation SLA you have up to 365 days to complete the version bump to 12.6.4+; realistically fold it into the next dependency sweep this quarter since it is a semver-safe patch. Do not treat this as a fire drill — the preconditions are too narrow — but do not let it rot in the backlog if you run a multi-tenant gateway.

Sources

  1. GHSA-v574-6498-x57v — Fastify Security Advisory
  2. fastify-reply-from repository
  3. fastify-reply-from releases
  4. OpenJS Foundation CVE Numbering Authority
  5. CWE-441: Unintended Proxy or Intermediary
  6. CISA KEV Catalog
  7. Snyk npm: fastify/reply-from
  8. Related — CVE-2026-33805 Connection header abuse in reply-from
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.