A bouncer who checks your ID but forgets to re-encode it before writing your name on the guest list lets you add VIP room numbers to your wristband
@fastify/http-proxy 9.0.0 through 11.6.5 substitutes URL-decoded route parameters into rewritePrefix without re-encoding them. If an application pins clients to a fixed upstream path via parameterized prefix plus rewritePrefix, an attacker can send a route parameter containing encoded /, ?, or # so it decodes into real path separators and delimiters inside the upstream URI. This injects extra upstream path segments and strips the mandatory suffix the rewritePrefix was meant to enforce, without any dot-dot traversal. The result is an upstream path outside the intended prefix.
Vendor CRITICAL 9.1 is defensible for the vulnerable configuration: unauthenticated, low complexity, high confidentiality and integrity impact. But real-world severity is lower because this bug only manifests when the app uses parameterized prefix plus rewritePrefix to pin a fixed upstream path—a non-default pattern. Many installs are internal or use static prefixes, and the practical impact depends entirely on what the upstream exposes. We downgrade to HIGH, not because the bug is hard to exploit, but because the reachable vulnerable population is a subset of all deployments.
5 steps from start to impact.
Locate a Fastify app using parameterized prefix plus rewritePrefix
@fastify/http-proxy with a route like /proxy/:p and a rewritePrefix that pins :p into a fixed upstream path. Attackers can use Burp or manual curl to map routes and identify parameterized proxy endpoints.- Public reachability to the Fastify app
- App declares a parameterized prefix route
- App sets
rewritePrefixto substitute the route parameter into a fixed upstream path
- Most @fastify/http-proxy deployments use static prefixes or no rewritePrefix; this pinning pattern is only a subset.
- Many Fastify apps sit behind an API gateway and are not directly reachable.
Send a route parameter with encoded separators and delimiters
curl --path-as-is or Burp Repeater to send a parameter value such as %2Fadmin%3Fstrip= or %2Finternal%23 to inject / and ?/# after URL decoding. The plugin decodes the parameter before substituting it into rewritePrefix, so the upstream URI gains real path separators and truncates the mandatory suffix.- Attacker can control the parameterized prefix value
- Upstream server interprets the decoded parameter as path rather than query or fragment
- Reverse proxies or WAFs that reject encoded slash sequences may block some payloads, but bypasses exist.
- The app must not separately URL-encode or sanitize the route parameter; many apps do not because the plugin decodes internally.
../ traversal.Craft a valid upstream URI outside the intended prefix
- Upstream server trusts the decoded path after substitution
- No additional path normalization on the upstream that would strip the injected segments
- Some upstream servers normalize paths differently; but many Node.js or nginx upstreams preserve the decoded path.
- Application-specific routing may not have a sensitive destination, limiting impact.
Abuse the upstream misroute for SSRF or authorization bypass
- Upstream has sensitive or privileged paths reachable via the proxy
- Upstream does not independently enforce path-based authentication for the injected route
- If upstream is a static backend with no sensitive paths, impact is limited.
- Network egress controls may limit SSRF to internal metadata endpoints.
Pivot through SSRF or stolen upstream access
- Upstream routes contain sensitive data or privileged actions
- No second factor or IP allowlisting on the upstream
- Modern cloud metadata endpoints often require IMDSv2 tokens, adding friction.
- Internal segmentation may block lateral movement from the proxy host.
%2f, %3f, and %23 in the parameterized prefix segment. This reduces unauthenticated remote reachability but may be bypassable.- Blocking
../sequences at WAF/IDS: this attack does not use dot-dot traversal. - URL-encoding route parameters at the application layer alone: the plugin decodes parameters before substitution, so app-level encoding may not survive unless done after plugin handling.
- Relying on TLS or network segmentation alone: an attacker hitting the public Fastify endpoint still reaches the proxied upstream over the app's trusted channel.
The supporting signals.
| In-the-wild status | No known exploitation; not in CISA KEV as of this assessment. |
|---|---|
| Proof-of-concept availability | Trivial to reproduce with curl --path-as-is or Burp Repeater once the vulnerable route is found; no dedicated public PoC repository required. |
| EPSS score / percentile | Not present in FIRST EPSS as of assessment; no EPSS percentile available. |
| KEV status | Not listed in CISA Known Exploited Vulnerabilities. |
| CVSS vector interpretation | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N — network, low complexity, no privileges, no user interaction, high confidentiality and integrity impact, scope unchanged. |
| Affected versions | 9.0.0 through 11.6.5. |
| Fixed versions | 11.6.6. |
| Scanning / exposure data | No Shodan/GreyNoise fingerprints for this application-level config; exposed Fastify apps are only visible if directly Internet-facing. |
| Disclosure date | 2026; exact date not supplied in the official record. |
| Reporting researcher/org | Not identified in the official record. |
Sources.
Why this verdict
- Config prerequisite: Only applications using parameterized
prefixplusrewritePrefixto pin a fixed upstream path are affected; this is a non-default subset of @fastify/http-proxy deployments. - Reachability friction: Many Fastify apps sit behind API gateways or are internal-only, reducing unauthenticated remote exposure.
- Role multiplier: @fastify/http-proxy is an application-tier library, not a canonical high-value component (IdP, PAM, hypervisor, CA, edge appliance). Even as an SSRF primitive, the blast radius is limited to the upstream services the app already proxies; it does not directly yield domain, fleet, or supply-chain compromise in the typical installation.
- Impact dependency: High confidentiality/integrity only if the upstream exposes sensitive or privileged paths. If upstream is a static or low-value service, impact drops materially.
Why not higher?
Not a default-exploit-everywhere bug: it requires a specific parameterized prefix plus rewritePrefix configuration and public reachability. The vendor CRITICAL 9.1 assumes every install has that pattern and a sensitive upstream, which overstates the real deployed population. No KEV or active exploitation evidence pushes back on top-end severity.
Why not lower?
When the vulnerable configuration is present, the exploit is unauthenticated, low complexity, and requires no dot-dot traversal, so common path-based detection misses it. The result can be upstream path restriction bypass, SSRF, or authorization bypass in the proxied service, with high confidentiality/integrity impact. There is no workaround other than upgrade.
Crowdsourced verification payload.
Run this on the application host or CI runner inside the project directory. Requires Node.js and read access to package.json / node_modules. Invoke: bash check_cve_2026_120213.sh.
#!/usr/bin/env bash
set -euo pipefail
if command -v npm >/dev/null 2>&1; then
version=$(npm list @fastify/http-proxy --depth=0 --parseable 2>/dev/null | awk -F '@' '/@fastify\/http-proxy@/{print $3; exit}')
elif [ -f node_modules/@fastify/http-proxy/package.json ]; then
version=$(node -p "require('./node_modules/@fastify/http-proxy/package.json').version")
else
echo "UNKNOWN"
exit 2
fi
if [ -z "$version" ]; then
echo "UNKNOWN"
exit 2
fi
node -e '
const v = process.argv[1];
const parts = v.split("-")[0].split(".").map(Number);
let status = "PATCHED";
let code = 0;
if (parts[0] < 9) {
status = "PATCHED"; code = 0;
} else if (parts[0] > 11 || (parts[0] === 11 && parts[1] > 6) || (parts[0] === 11 && parts[1] === 6 && parts[2] >= 6)) {
status = "PATCHED"; code = 0;
} else {
status = "VULNERABLE"; code = 1;
}
console.log(status);
process.exit(code);
' "$version"