GHSA-hrwm-hgmj-7p9c: @fastify/express's middleware path doubling causes authentication bypass in child plugin scopes
Summary`@fastify/express` v4.0.4 contains a path handling bug in the `onRegister` function that causes middleware paths to be doubled when inherited by child plugins. This results in complete bypass of Express middleware security controls for all routes defined within child plugin scopes that share a prefix with parent-scoped middleware. No special configuration is required — this affects the default Fastify configuration.DetailsThe vulnerability exists in the `onRegister` function at `index.js` lines 92-101. When a child plugin is registered with a prefix, the `onRegister` hook copies middleware from the parent scope and re-registers it using `instance.use(...middleware)`. However, the middleware paths stored in `kMiddlewares` are already prefixed from their original registration.The call flow demonstrates the problem:Parent scope registers middleware: `app.use('/admin', authFn)` — `use()` calculates path as `'' + '/admin' = '/admin'` — stores `['/admin', authFn]` in `kMiddlewares`Child plugin registers with `{ prefix: '/admin' }` — triggers `onRegister(instance)``onRegister` copies parent middleware and calls `instance.use('/admin', authFn)` on childChild's `use()` function calculates path as `'/admin' + '/admin' = '/admin/admin'` — registers middleware with doubled pathRoutes in child scope use the child's Express instance, where middleware is registered under the incorrect path `/admin/admin`Requests to `/admin/secret` don't match `/admin/admin` — middleware is silently skippedThe root cause is in the `use()` function at lines 25-26, which always prepends `this.prefix` to string paths, combined with `onRegister` re-calling `use()` with already-prefixed paths.PoCActual output:ImpactComplete bypass of Express middleware security controls for all routes defined in child plugin scopes. Authentication, authorization, rate limiting, CSRF protection, audit logging, and any other middleware-based security mechanisms are silently skipped for affected routes.No special request crafting is required — normal requests bypass the middlewareIt affects the idiomatic Fastify plugin pattern commonly used in productionThe bypass is silent with no errors or warningsDevelopers' basic testing of root-scoped routes will pass, masking the vulnerabilityAny child plugin scope that shares a prefix with middleware is affectedApplications using `@fastify/express` with path-scoped middleware and child plugins with matching prefixes are vulnerable in default configurations.Affected Versions`@fastify/express` v4.0.4 (latest at time of discovery)Fastify 5.x in default configurationNo special router options required (`ignoreDuplicateSlashes` not needed)Affects any child plugin registration where the prefix overlaps with middleware path scopingDoes NOT affect middleware registered without path scoping (global middleware)Does NOT affect middleware registered on root path (`/`) due to special case handlingVariant Testing| Scenario | Middleware Path | Child Prefix | Result |
|---|---|---|---|
| Root route `/admin/root-data` | `/admin` | N/A | Middleware runs (403) |
| Child route `/admin/secret` | `/admin` | `/admin` | **BYPASS** (200) |
| Child route `/api/data` | `/api` | `/api` | **BYPASS** (200) |
| Nested child `/admin/sub/data` | `/admin` | `/admin/sub` | **BYPASS** — path becomes `/admin/sub/admin` |
| Middleware on `/` with any child | `/` | `/api` | No bypass — `path === '/' && prefix.length > 0` special case |Suggested FixThe `onRegister` function should store and re-use the original unprefixed middleware paths, or avoid re-calling the `use()` function entirely. Options include:Store the original path and function separately in `kMiddlewares` before prefixingStrip the parent prefix before re-registering in child scopesStore already-constructed Express middleware objects rather than re-processing paths
CVSS v3.1: 9.1