Developers using @fastify/express version 4.0.4 face a serious authentication bypass vulnerability. Attackers can skirt path-specific middleware, like login checks on /admin routes, by exploiting URL normalization differences between Fastify’s router and Express middleware. This hits when Fastify enables duplicate slash ignoring or semicolon delimiters—options many turn on for real-world URL handling.
The plugin bridges Express middleware into Fastify apps. Fastify’s find-my-way router normalizes quirky URLs to match routes securely. But @fastify/express hands the raw, unnormalized URL to Express layers. Result: middleware skips checks, handlers run unauthenticated. Affects setups with ignoreDuplicateSlashes: true or useSemicolonDelimiter: true. As of now, no patch exists in v4.0.4.
Vector Breakdown
First vector: duplicate slashes. Set ignoreDuplicateSlashes: true in Fastify’s router options. Client hits GET //admin/dashboard. Fastify strips the extra slash, matches /admin/dashboard route. Then @fastify/express‘s enhanceRequest function runs:
const decodedUrl = decodeURI(url)
req.raw.url = decodedUrl
decodeURI only decodes percent escapes—it ignores slashes. Express gets //admin/dashboard. A middleware like app.use('/admin', authMiddleware) checks prefixes. Express’s old path-to-regexp v0.1.12 expects /admin followed by slash or end. //admin fails the regex /^\/admin\/?(?=\/|$)/i. Bypass complete.
Second vector: semicolons. Enable useSemicolonDelimiter: true. Request GET /admin;bypass. Fastify’s safeDecodeURI treats ; as query splitter: path becomes /admin, query bypass. Route matches. Express still sees full /admin;bypass. That semicolon breaks the lookahead in Express’s regex—no trailing slash or end after /admin. Middleware skips again.
This isn’t the first rodeo. v4.0.3 fixed percent-decoding bypass (GHSA-g6q3-96cp-5r5m, CVE-2026-22037). @fastify/middie patched similar gaps (GHSA-8p85-9qpw-fwgw, CVE-2026-2880). But @fastify/express lags, exposing a design rift: Fastify prioritizes performance and spec compliance, Express clings to legacy parsing.
Why This Bites—and What to Do
Fastify powers high-traffic Node.js apps—npm downloads top 1 million weekly for the core, thousands for this plugin. Teams mix Express middleware for auth (Passport, helmet), rate limiting, or logging on admin paths. Bypass means attackers hit sensitive endpoints: dashboards, APIs, user data. Real-world URLs often carry these quirks from proxies, CDNs, or bots. HTTP specs tolerate // (RFC 3986), semicolons in paths (matrix params, old-school). Disabling normalizations fixes it but breaks compatibility.
Prevalence? Scan your deps: npm ls @fastify/express. If v4.0.4 and router options on, test vectors. Proof-of-concept: spin a minimal app, add app.use('/admin', (req, res, next) => { console.log('Auth hit'); next(); }), route fastify.get('/admin/dashboard', handler). Hit //admin/dashboard—no log, handler fires.
Mitigation now: downgrade to v4.0.2 (pre-percent fix, but no normalization vuln), switch to @fastify/middie, or patch locally by normalizing in enhanceRequest:
// Quick fix - add before req.raw.url assignment
const normalized = decodedUrl.replace(/\/+/g, '/').split(';')[0];
req.raw.url = normalized;
Report to maintainers—GitHub issue pending. Watch for v4.0.5. Skeptical note: not zero-days everywhere. Default Fastify skips these options, so vanilla setups safe. But production tunes for robustness, hitting this snag. Bridges like this highlight framework friction: Fastify’s speed vs. Express’s ecosystem. Vet plugins hard; auth belongs in handlers or central guards, not brittle middleware stacks.
Bottom line: audit now. One malformed URL flips protected to public. In crypto, finance, security apps—where Njalla operates—this escalates from annoyance to breach vector. Act before scanners flag it.