Skip to content

fix: normalize all-slash mount paths in app.use to '/'#7286

Open
youcefzemmar wants to merge 1 commit into
expressjs:masterfrom
youcefzemmar:fix/app-use-path-does-not-mount-at
Open

fix: normalize all-slash mount paths in app.use to '/'#7286
youcefzemmar wants to merge 1 commit into
expressjs:masterfrom
youcefzemmar:fix/app-use-path-does-not-mount-at

Conversation

@youcefzemmar
Copy link
Copy Markdown

## What

Normalize string mount paths that are only slashes (`'/'`, `'//'`, `'///'`, …) to `'/'` inside `app.use()` before handing them to the underlying router.

Fixes #4557`app.use('//', subApp)` no longer takes a divergent path through the router that depends on `path-to-regexp` matching the empty pattern.

## Root cause

`router`'s `Layer` constructor sets a `/`-fast-path flag with `this.slash = path === '/' && opts.end === false`, then runs the pattern through `loosen()`:

```js
const TRAILING_SLASH_REGEXP = /\/+$/
// ...
return String(path).replace(TRAILING_SLASH_REGEXP, '')

For '//', loosen strips both slashes to ''. The layer's slash flag is false (because path !== '/'), so matching falls through to pathRegexp.match('', { end: false, trailing: true }). That matcher matches every request — the layer behaves like a wildcard prefix, just without the fast-path metadata. Mounting at '///' and longer reduces to the same case.

In the issue, the reporter mounted dev/test routes under '//' expecting them to be unreachable from /, but the layer matched anyway and exposed those routes.

Fix

One guard, right after app.use resolves the path arg:

if (typeof path === 'string' && /^\/+$/.test(path)) {
  path = '/';
}

The fix stays scoped to all-slash strings. Inner repeated slashes like '/foo//bar' are deliberately left alone — collapsing them is a broader behavior change, the same one that took PR #7054 out of scope.

Testing

  • npm test — 1250 passing, including the new should treat all-slash paths as a root mount case in test/app.use.js that exercises '//' and '///' end-to-end.
  • npm run lint — clean.

The new test locks in three things: / and /secret reach the sub-app, an unrelated path like /missing still 404s (i.e. the layer is not a global wildcard), and '///' normalizes the same way as '//'.

Notes

Root cause technically lives in router's loosen() (treating '//' as ''). Fixing it in express is a surface workaround — if router ever normalizes this itself, the guard here is harmless but redundant.

Fixes #4557.

Mounting with `app.use('//', subApp)` previously slipped past the
router's `/`-fast-path because `loosen('//')` strips trailing slashes
to `''`, leaving an empty pattern that `path-to-regexp` happily matches
against every request. The router then treats the layer as "any path"
without setting the `slash` fast-path flag — a behavior that diverges
from `app.use('/', subApp)` and surprised the reporter in expressjs#4557.

Collapse any all-slash string path (`'/'`, `'//'`, `'///'`, …) to `'/'`
in `app.use` before handing it to the router. Inner repeated slashes
(`'/foo//bar'`) are intentionally left alone — that is a broader
behavior change outside the scope of this issue.

Fixes expressjs#4557.
@youcefzemmar youcefzemmar marked this pull request as ready for review May 28, 2026 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App.use(path, ...) Does Not Mount at '//'

1 participant