high · 7.5CVE-2026-15074Jul 24, 2026

CVE-2026-15074: @fastify/static Route Guard Bypass via Dot-Dot Path Traversal

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A flaw in @fastify/static lets attackers sneak dot-dot path segments past route-level authentication guards to read protected static files, without ever leaving the configured static root.

Package@fastify/static
Ecosystemnpm
Affected<= 10.1.0
Fixed in10.1.1
CVE-2026-15074: @fastify/static Route Guard Bypass via Dot-Dot Path Traversal

The problem

@fastify/static versions up to and including 10.1.0 do not reject .. or %2E%2E segments before resolving a file path. The router (find-my-way) does not normalize these segments during route matching, so a request like /foo/../deep/secret.txt hits the plugin's catch-all instead of the protected /deep/* route.

This is a bypass of the earlier fix for GHSA-x428-ghpx-8j92 (CVE-2026-6414). That fix only blocked encoded slashes (%2F). Dot-dot segments survive the decodeURI + encodeURI round-trip intact and are collapsed silently by @fastify/send's internal path.normalize, defeating any route-scoped middleware or guard that ran before the file is served.

The impact is limited to route-guard bypass within the configured static root. An attacker cannot escape the root directory using this bug alone, but they can read files under a path prefix that should have been blocked by middleware.

Proof of concept

A working proof-of-concept for CVE-2026-15074 in @fastify/static, with the exact payload below.

http
GET /public/../deep/secret.txt HTTP/1.1
Host: target.example.com

# Percent-encoded variant also works:
GET /public/%2E%2E/deep/secret.txt HTTP/1.1
Host: target.example.com

The getPathnameForSend helper (introduced to fix the prior %2F bypass) ran decodeURI then encodeURI on the incoming pathname. Both .. and %2E%2E survive that round-trip unchanged because they contain no characters that encodeURI encodes, so neither form was stripped.

Downstream, @fastify/send calls path.normalize on the pathname before its own traversal guard, which collapses foo/../deep into deep. By then, route-level guards have already run against the original un-normalized URL and did not match /deep/*, so access control is silently skipped.

The patch adds an explicit check in getPathnameForSend to detect and reject any pathname containing .. or %2E%2E segments before the path ever reaches @fastify/send, closing the gap that the previous fix left open. CWE-22 (Improper Limitation of a Pathname to a Restricted Directory).

The fix

Upgrade @fastify/static to version 10.1.1. If you cannot upgrade immediately, do not rely on Fastify route-based middleware or guards to protect files served by @fastify/static; move that logic into the static plugin's own allowedPath callback or a server-level hook that runs after normalization.

Reported by imssm99.

References: [1][2][3][4][5][6]

Related research