Skip to content

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

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

fix(auth0-express): harden createRouteUrl against path injection attacks#23
cschetan77 wants to merge 2 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 (their origin is null or a different host), 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 used by both createRouteUrl and toSafeRedirect, preventing future validation drift. The regex is tightened to /^[a-zA-Z][a-zA-Z0-9.+-]*/ per RFC 3986 — a scheme must start with a letter, so paths like /2024-report:summary or :param are correctly treated as relative paths rather than being misidentified as schemes.

createRouteUrl

Adds an explicit scheme check using SCHEME_REGEX on the normalised path before passing it to new URL(). If a scheme is detected the function throws immediately — failing fast rather than relying solely on the post-construction origin check.

toSafeRedirect

toSafeRedirect accepts user-supplied input which can legitimately be an absolute same-origin URL (e.g. returnTo=https://myapp.com/profile). Since createRouteUrl now rejects any scheme, toSafeRedirect is updated to branch on whether the input carries a scheme:

  • Has a scheme → parsed directly with new URL() and validated by origin check.
  • No scheme → treated as a relative path and passed through the strict createRouteUrl validation.

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

callback-handler.ts

req.url is normally an origin-form path (/auth/callback?...) but can arrive as a full absolute URL when a request is forwarded by a reverse proxy. createRouteUrl is designed for SDK config paths (always relative) and its scheme check would throw on absolute-form request targets, causing a 500.

The handler now uses new URL(req.url, appBaseUrl) directly with an explicit origin check, which correctly handles both forms. Any parse error is caught by the existing outer catch and returned as a 500.

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
Absolute URL injection https://evil.com/path Scheme check
javascript: injection javascript:alert(1) Scheme check
data: injection data:text/html,<script>...</script> Scheme check
file: injection file:///etc/passwd Scheme check
ASCII whitespace before scheme \thttps://evil.com Origin check (safety net)
External absolute URL in redirect https://evil.com Origin check

Testing

Unit Tests (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
  • throws when path is an absolute URL with a scheme — now expects scheme-specific error message
  • throws when path uses http scheme to override the base URL origin — now expects scheme-specific error message
  • throws when path uses javascript schemejavascript:alert(1)
  • throws when path uses data schemedata:text/html,...
  • throws when path uses file schemefile:///etc/passwd
  • 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 — double backslash and mixed slash/backslash cases
  • 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
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