GitHub Security Advisory: GHSA-6hw5-45gm-fj88
Package: @fastify/express (NPM)
Severity: CRITICAL (CVSS 0.0)
CWE: CWE-436: Interpretation Conflict
Vulnerable versions: <= 4.0.4
Patched in: 4.0.5

Summary: @fastify/express has a middleware authentication bypass via URL normalization gaps (duplicate slashes and semicolons)

Details:
### Summary

`@fastify/express` v4.0.4 fails to normalize URLs before passing them to Express middleware when Fastify router normalization options are enabled. This allows complete bypass of path-scoped authentication middleware via two vectors:

1. **Duplicate slashes** (`//admin/dashboard`) when `ignoreDuplicateSlashes: true` is configured
2. **Semicolon delimiters** (`/admin;bypass`) when `useSemicolonDelimiter: true` is configured

In both cases, Fastify's router normalizes the URL and matches the route, but `@fastify/express` passes the original un-normalized URL to Express middleware, which fails to match and is skipped.

Note: This is distinct from GHSA-g6q3-96cp-5r5m (CVE-2026-22037), which addressed URL percent-encoding bypass and was patched in v4.0.3. These normalization gaps remain in v4.0.4. A similar class of normalization issue was addressed in `@fastify/middie` via GHSA-8p85-9qpw-fwgw (CVE-2026-2880), but `@fastify/express` does not include the equivalent fixes.

### Details

The vulnerability exists in `@fastify/express`'s `enhanceRequest` function (`index.js` lines 43-46):

```javascript
const decodedUrl = decodeURI(url)
req.raw.url = decodedUrl
```

The `decodeURI()` function only handles percent-encoding — it does not normalize duplicate slashes or strip semicolon-delimited parameters. When Fastify's router options are enabled, `find-my-way` applies these normalizations during route matching, but `@fastify/express` passes the original URL to Express middleware.

#### Vector 1: Duplicate Slashes

When `ignoreDuplicateSlashes: true` is set, Fastify's `find-my-way` router normalizes `//admin/dashboard` to `/admin/dashboard` for route matching. However, Express middleware receives `//admin/dashboard`. Express's `app.use('/admin', authMiddleware)` expects paths to start with `/admin/`, but `//admin` does not match the `/admin` prefix pattern.

The attack sequence:
1. Client sends `GET //admin/dashboard`
2. Fastify's router normalizes this to `/admin/dashboard` and finds a matching route
3. `enhanceRequest` sets `req.raw.url = "//admin/dashboard"` (preserves double slash)
4. Express middleware `app.use('/admin', authMiddleware)` does not match `//admin` prefix
5. Authentication is bypassed, and the Fastify route handler executes

#### Vector 2: Semicolon Delimiters

When `useSemicolonDelimiter: true` is configured, the router uses `find-my-way`'s `safeDecodeURI()` which treats semicolons as query string delimiters, splitting `/admin;bypass` into path `/admin` and querystring `bypass` for route matching. However, `@fastify/express` passes the full URL `/admin;bypass` to Express middleware.

Express uses path-to-regexp v0.1.12 internally, which compiles middleware paths like `/admin` to the regex `/^\/admin\/?(?=\/|$)/i`. A semicolon character does not satisfy the lookahead condition, causing the middleware match to fail.

The attack flow:
1. Request `GET /admin;bypass` arrives
2. Fastify router: splits at `;` — matches route `GET /ad