SIGN IN SIGN UP
apache / superset UNCLAIMED

Apache Superset is a Data Visualization and Data Exploration Platform

0 0 151 TypeScript

fix(security): harden navigation guards against backslash open-redirects

AF-1 (dual-lane adversarial review, 2026-05-19): the three new navigation
guards introduced in this branch — `pathUtils.ensureAppRoot`,
`navigationUtils.SAFE_NAVIGATION_URL_RE`/`assertSafeNavigationUrl`, and
`RedirectWarning/utils.isAllowedScheme` — all only screened the leading
`//` form of a protocol-relative URL. Backslash variants (`/\evil.com`,
`\/evil.com`, `\\evil.com`) slipped past each: the regex's `^\/(?!\/)`
matched `/\` (2nd char is `\`, not `/`); `ensureAppRoot` and
`isAllowedScheme` only tested `startsWith('//')`; and `new URL('/\\…')`
throws, routing through `isAllowedScheme`'s `catch { return true }`
"relative — allow" branch.

Browsers normalise `/\` → `//` in the special-scheme authority, so on a
default (root-of-domain, empty app root) deployment, a consented click
through `RedirectWarning` resolved to `https://evil.com`. Every channel-3
helper (`openInNewTab`, `AppLink`, `getShareableUrl`, `redirect`) was
defeated.

Slice 1 lands all five hardening changes in one commit (same-commit
coupling per PLAN.md regression-test gate) so no intermediate state
leaves the user-facing interstitial misleading the user:

- `pathUtils.ts:41` `ensureAppRoot`: protocol-relative check now matches
  `/^[/\\][/\\]/`, so backslash variants are passed through unchanged
  for the downstream guard to reject instead of being laundered through
  the appRoot prefixing path.
- `navigationUtils.ts` `SAFE_NAVIGATION_URL_RE` / `assertSafeNavigationUrl`:
  rejects any URL containing `\` anywhere, and adds an authority-userinfo
  check (`new URL` parse → reject if `username` or `password` set) for
  `http(s)`/`ftp` schemes (closes the `https://good@evil.com` variant,
  nit-3).
- `navigationUtils.ts` `navigateTo` / `navigateWithState`: wired through
  `assertSafeNavigationUrl` (closes C2 transitively + nit-1's ~13 direct
  callers). `navigateTo` falls back to `ensureAppRoot('/')` with a
  `console.error` on block; `navigateWithState` no-ops + `console.error`
  (history API — no surprise full-page nav).
- `RedirectWarning/utils.ts:46` `isAllowedScheme`: backslash rejection
  moved BEFORE the `new URL` try-block so a URL-constructor throw on
  `/\evil.com` cannot fall through `catch { return true }`.
- `RedirectWarning/index.tsx`: when `isAllowedScheme(targetUrl)` returns
  false, renders a visible "Unsafe link blocked" Card with no Continue
  button instead of the standard "External link warning" Card. The
  interstitial UI itself now refuses unsafe URLs rather than just
  silently no-opping the click.

Regression oracle (HEAD-verified scenario, pinned across all four guard
test files): path `/\evil.com`, both empty and `/superset/` app roots,
must reject independently at each guard site; the composition pin
`assertSafeNavigationUrl(ensureAppRoot('/\\evil.com'))` (asserted
transitively through the public `getShareableUrl` / `openInNewTab`
helpers since `assertSafeNavigationUrl` is module-private) must throw.

86 unit + integration assertions across 4 test files; 23 failing on
HEAD `1613e53aaf`, all green at this commit.

Refs: PR #39925, PLAN.md Slice 1 (round 6 + AF folds).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
J
Joe Li committed
9141324715e89aced5911e13e60df0f895f3530f
Parent: 1613e53