Skip to content

fix(auth0-express): harden createRouteUrl against path injection attacks#23

Open
cschetan77 wants to merge 6 commits into
mainfrom
fix/route-url-path-validation
Open

fix(auth0-express): harden createRouteUrl against path injection attacks#23
cschetan77 wants to merge 6 commits into
mainfrom
fix/route-url-path-validation

Conversation

@cschetan77

@cschetan77 cschetan77 commented Jul 8, 2026

Copy link
Copy Markdown

Problem

createRouteUrl is a utility that combines a relative route path with the application base URL. When the path argument can be influenced by an attacker, insufficient validation allows the base URL origin to be overwritten — enabling open redirect and URL injection attacks.

What was fixed in a prior commit (7c69b9b)

Commit 7c69b9b introduced two protections:

  1. Strip all leading forward slashes — changed ensureNoLeadingSlash from removing a single / to removing all of them, preventing protocol-relative URL attacks like ///evil.com.
  2. Origin check after construction — after calling new URL(path, base), the result's origin is compared to the base URL's origin and an error is thrown if they differ.

What remained unmitigated

Two gaps were left unaddressed:

1. Backslashes not stripped

ensureNoLeadingSlash only handled forward slashes. The WHATWG URL parser normalises backslashes to forward slashes, so inputs like \\evil.com or \/evil.com were treated as protocol-relative URLs (//evil.com) by the parser — overriding the base host.

2. No scheme check before URL construction

There was no pre-construction check for URL schemes. Inputs like javascript:alert(1), data:text/html,..., or file:///etc/passwd have no leading slashes so the multi-slash check was skipped entirely. While the origin check caught these, relying on a single post-construction safety net is insufficient defence-in-depth.

What this PR fixes

ensureNoLeadingSlash

Strips both leading forward slashes and backslashes using /^[/\\]+/, closing the backslash normalisation bypass. The * quantifier is also corrected to + (only strip when leading characters are actually present).

SCHEME_REGEX — shared named constant

The scheme detection regex is extracted to a single named constant shared by createRouteUrl and toSafeRedirect, preventing validation drift. The regex follows RFC 3986 — a scheme must start with a letter — so paths like /2024-report:summary or :param are not misidentified as schemes.

createRouteUrl

Now handles two input forms:

  • Absolute URL — if the input carries a scheme, it is parsed directly and the origin is validated against the base. This correctly handles absolute-form req.url values forwarded by reverse proxies without breaking subpath deployments.
  • Relative path — leading slashes and backslashes are stripped, a multi-leading check is applied, then the path is resolved against the full base URL (preserving any subpath).

toSafeRedirect

Accepts user-supplied input (e.g. a returnTo query parameter) which can be either a relative path or an absolute same-origin URL. Branches on whether the input carries a scheme:

  • Has a scheme → parsed directly with new URL() and validated by origin check.
  • No scheme → passed through createRouteUrl so that the subpath in the base URL is preserved when resolving relative paths.

new URL(safeBaseUrl).origin is inside the try block so a malformed safeBaseUrl returns undefined instead of throwing uncaught.

Attack vectors guarded against

Attack Example Blocked by
Protocol-relative via multiple / //evil.com, ///evil.com Multi-leading slash check
Backslash normalisation bypass \\evil.com, \/evil.com, /\evil.com Multi-leading slash/backslash check
External absolute URL https://evil.com/path Origin check
javascript: injection javascript:alert(1) Origin check (null origin)
data: injection data:text/html,<script>...</script> Origin check (null origin)
file: injection file:///etc/passwd Origin check (null origin)
ASCII whitespace before scheme \thttps://evil.com Origin check
External absolute URL in redirect https://evil.com Origin check

Testing

New and updated test cases (packages/auth0-express/src/utils.spec.ts):

  • throws when path has multiple leading slashes — error message updated to include backslashes
  • throws when path has multiple leading backslashes\\evil.com
  • throws when path has mixed leading slash and backslash/\evil.com, \/evil.com
  • accepts an absolute same-origin URL — reverse proxy origin-form and subpath cases
  • throws when absolute URL has a different origin — replaces old scheme-specific error message
  • throws when path uses http/javascript/data/file scheme — all blocked by origin check
  • single leading backslash is stripped and resolves as a same-origin relative path — documents safe asymmetry
  • allows paths with a colon in non-first segment/a/b:c
  • allows paths whose first segment starts with a digit followed by a colon/2024-report:summary
  • toSafeRedirect — backslash-based bypass attempts blocked
  • toSafeRedirect — returns undefined when safeBaseUrl is not a valid URL

Strip leading backslashes in addition to forward slashes to prevent
WHATWG URL parser normalisation bypasses, and add an explicit scheme
check before URL construction to reject javascript:, data:, file: and
other scheme-based injection attempts as defence-in-depth.

toSafeRedirect is updated to branch on whether input carries a scheme:
absolute URLs are validated via origin check directly, while relative
paths continue through the strict createRouteUrl validation.
…ardening

- Fix scheme regex from /^[a-zA-Z0-9.+-]*:/ to /^[a-zA-Z][a-zA-Z0-9.+-]*:/
  so paths with a digit-start first segment (e.g. /2024-report:summary) or
  a bare leading colon (:param) are not misidentified as URL schemes
- Extract SCHEME_REGEX to a named constant shared by createRouteUrl and
  toSafeRedirect to prevent validation drift
- Fix ensureNoLeadingSlash: change * to + and remove the dead g flag
- Move new URL(safeBaseUrl).origin inside the try block in toSafeRedirect
  so a malformed safeBaseUrl returns undefined instead of throwing uncaught
- Replace createRouteUrl(req.url, appBaseUrl) in callback-handler with
  new URL(req.url, appBaseUrl) + origin check, so absolute-form request
  targets forwarded by reverse proxies are handled correctly
- Add tests for single leading backslash, digit-start colon path, colon in
  non-first segment, toSafeRedirect backslash cases, and malformed safeBaseUrl
createRouteUrl now accepts absolute same-origin URLs (e.g. from a reverse
proxy) by branching on whether the input carries a scheme: absolute inputs
are parsed directly and validated by origin check, relative inputs go through
the existing slash-stripping and multi-leading check. This fixes the subpath
deployment regression where new URL('/auth/callback', base) was dropping the
subpath, causing redirect_uri mismatches and invalid_grant errors.

callback-handler.ts reverts to using createRouteUrl so subpath handling is
preserved and there is a single authoritative URL resolution helper.

toSafeRedirect is simplified to new URL(input, base) + origin check —
new URL() already handles both absolute and relative inputs natively, making
the previous scheme-branching logic redundant.
@cschetan77 cschetan77 force-pushed the fix/route-url-path-validation branch from 3e822ff to f6e10b6 Compare July 13, 2026 05:22
Simplifying toSafeRedirect to new URL(input, base) dropped the subpath
when resolving relative paths — new URL('/auth/callback', 'https://myapp.com/subapp')
ignores the subpath, producing https://myapp.com/auth/callback instead of
https://myapp.com/subapp/auth/callback. Restore the scheme-branching so
relative paths go through createRouteUrl which strips the leading slash
before resolving against the full base, preserving the subpath.
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.

1 participant