Skip to content

feat(router-core, react-router, solid-router, vue-router): isDangerousProtocol uses customizable allowlist#6542

Merged
Sheraff merged 10 commits intomainfrom
feat-router-core-url-protocol-blocklist
Feb 15, 2026
Merged

feat(router-core, react-router, solid-router, vue-router): isDangerousProtocol uses customizable allowlist#6542
Sheraff merged 10 commits intomainfrom
feat-router-core-url-protocol-blocklist

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Jan 28, 2026

  • there can be an infinity of protocols (custom protocols are a thing)
  • blob: and data: are widely used protocols (so we should have a way to allow them), but they can also be considered dangerous if displaying untrusted links (so we should have a way to block them)
  • an allowlist is usually a better security practice than a blocklist
  • causing security issues is bad (block everything)
  • breaking links on the web is bad (allow everything)

List of CVEs about unsafe URL protocols:

Summary by CodeRabbit

  • New Features

    • Added a configurable protocolAllowlist option and exposed DEFAULT_PROTOCOL_ALLOWLIST for React, Solid, and Vue packages.
  • Behavior Changes

    • Link, navigation, and redirect protocol checks now honor the router allowlist; redirects with disallowed protocols are rejected and warnings updated.
  • Documentation

    • Docs updated with protocolAllowlist usage, defaults, and examples.
  • Tests

    • Expanded tests for allowed/blocked protocols, custom allowlists, and integration with router behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Add a configurable protocol allowlist to router-core (DEFAULT_PROTOCOL_ALLOWLIST), change isDangerousProtocol to accept an allowlist Set, propagate the allowlist into link/redirect checks across adapters, remove the old hardcoded SAFE_URL_PROTOCOLS guard in redirect creation, and export the default allowlist.

Changes

Cohort / File(s) Summary
Core utils & exports
packages/router-core/src/utils.ts, packages/router-core/src/index.ts
Introduce DEFAULT_PROTOCOL_ALLOWLIST and change isDangerousProtocol(url)isDangerousProtocol(url, allowlist: Set<string>); re-export the default allowlist.
Router implementation
packages/router-core/src/router.ts, packages/router-core/src/redirect.ts
Add RouterOptions.protocolAllowlist?: Array<string> and internal RouterCore.protocolAllowlist: Set<string>; initialize from options; pass allowlist into dangerous-protocol checks; redirect now throws on disallowed protocols.
Framework adapters — link handling
packages/react-router/src/link.tsx, packages/solid-router/src/link.tsx, packages/vue-router/src/link.tsx
Update internal/external link checks to call isDangerousProtocol(..., router.protocolAllowlist) and include the allowlist in hook dependency arrays where applicable.
Public re-exports
packages/react-router/src/index.tsx, packages/solid-router/src/index.tsx, packages/vue-router/src/index.tsx
Re-export DEFAULT_PROTOCOL_ALLOWLIST from @tanstack/router-core in adapter packages.
Docs
docs/router/framework/react/api/router/RouterOptionsType.md
Document new protocolAllowlist RouterOptions property (optional Array, defaults to DEFAULT_PROTOCOL_ALLOWLIST) with examples and default entries.
Tests
packages/router-core/tests/dangerous-protocols.test.ts
Update tests to use DEFAULT_PROTOCOL_ALLOWLIST and new isDangerousProtocol(..., allowlist) signature; expand/rename tests for allowlist semantics and relax redirect-creation expectations to match validation timing.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant User
participant Link as LinkComponent
participant Router as RouterCore
participant Utils as isDangerousProtocol
User->>Link: render / click (to/href)
Link->>Utils: isDangerousProtocol(url, router.protocolAllowlist)
Utils-->>Link: boolean (dangerous?)
alt not dangerous
Link->>Router: navigate(to/href)
Router->>Utils: isDangerousProtocol(url, Router.protocolAllowlist)
Utils-->>Router: boolean (dangerous?)
alt not dangerous
Router-->>User: perform navigation / redirect
else dangerous
Router-->>User: throw/error (blocked protocol)
end
else dangerous
Link-->>User: warn or prevent navigation
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • birkskyum

Poem

"I hopped through code and raised a list,
Let safe schemes pass and kept the rest mist.
Defaults set, new checks in sight,
Links consult the rabbit's light.
Secure small hops, through day and night 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (318 files):

⚔️ docs/router/framework/react/api/router/NotFoundErrorType.md (content)
⚔️ docs/router/framework/react/api/router/RouterOptionsType.md (content)
⚔️ docs/router/framework/react/guide/document-head-management.md (content)
⚔️ docs/router/framework/react/how-to/setup-ssr.md (content)
⚔️ docs/router/framework/react/how-to/test-file-based-routing.md (content)
⚔️ docs/router/framework/react/how-to/use-environment-variables.md (content)
⚔️ docs/start/config.json (content)
⚔️ docs/start/framework/react/getting-started.md (content)
⚔️ docs/start/framework/react/guide/hosting.md (content)
⚔️ docs/start/framework/react/guide/isr.md (content)
⚔️ docs/start/framework/react/guide/middleware.md (content)
⚔️ docs/start/framework/react/guide/server-entry-point.md (content)
⚔️ docs/start/framework/react/quick-start.md (content)
⚔️ docs/start/framework/solid/guide/hosting.md (content)
⚔️ docs/start/framework/solid/quick-start.md (content)
⚔️ e2e/react-router/basic-file-based-code-splitting/src/routeTree.gen.ts (content)
⚔️ e2e/react-router/basic-file-based-code-splitting/src/routes/__root.tsx (content)
⚔️ e2e/react-router/basic-file-based/src/routeTree.gen.ts (content)
⚔️ e2e/react-router/basic-file-based/src/routes/params-ps/index.tsx (content)
⚔️ e2e/react-router/basic-file-based/tests/params.spec.ts (content)
⚔️ e2e/react-start/basic-react-query/package.json (content)
⚔️ e2e/react-start/basic-tsr-config/package.json (content)
⚔️ e2e/react-start/basic/package.json (content)
⚔️ e2e/react-start/basic/tests/open-redirect-prevention.spec.ts (content)
⚔️ e2e/react-start/csp/package.json (content)
⚔️ e2e/react-start/css-modules/package.json (content)
⚔️ e2e/react-start/custom-basepath/package.json (content)
⚔️ e2e/react-start/scroll-restoration/package.json (content)
⚔️ e2e/react-start/selective-ssr/package.json (content)
⚔️ e2e/react-start/serialization-adapters/package.json (content)
⚔️ e2e/react-start/server-functions-global-middleware/package.json (content)
⚔️ e2e/react-start/server-functions-global-middleware/src/routeTree.gen.ts (content)
⚔️ e2e/react-start/server-functions-global-middleware/src/routes/index.tsx (content)
⚔️ e2e/react-start/server-functions-global-middleware/src/start.ts (content)
⚔️ e2e/react-start/server-functions-global-middleware/tests/global-middleware.spec.ts (content)
⚔️ e2e/react-start/server-functions/package.json (content)
⚔️ e2e/react-start/server-functions/src/routes/abort-signal/$method.tsx (content)
⚔️ e2e/react-start/server-routes-global-middleware/package.json (content)
⚔️ e2e/react-start/server-routes/package.json (content)
⚔️ e2e/react-start/streaming-ssr/package.json (content)
⚔️ e2e/react-start/virtual-routes/package.json (content)
⚔️ e2e/react-start/website/package.json (content)
⚔️ e2e/solid-start/basic-auth/package.json (content)
⚔️ e2e/solid-start/basic-solid-query/package.json (content)
⚔️ e2e/solid-start/basic-tsr-config/package.json (content)
⚔️ e2e/solid-start/basic/package.json (content)
⚔️ e2e/solid-start/basic/tests/special-characters.spec.ts (content)
⚔️ e2e/solid-start/csp/package.json (content)
⚔️ e2e/solid-start/css-modules/package.json (content)
⚔️ e2e/solid-start/custom-basepath/package.json (content)
⚔️ e2e/solid-start/scroll-restoration/package.json (content)
⚔️ e2e/solid-start/selective-ssr/package.json (content)
⚔️ e2e/solid-start/serialization-adapters/package.json (content)
⚔️ e2e/solid-start/server-functions/package.json (content)
⚔️ e2e/solid-start/server-functions/src/routes/abort-signal/$method.tsx (content)
⚔️ e2e/solid-start/server-routes/package.json (content)
⚔️ e2e/solid-start/virtual-routes/package.json (content)
⚔️ e2e/solid-start/website/package.json (content)
⚔️ e2e/vue-start/basic-auth/package.json (content)
⚔️ e2e/vue-start/basic-tsr-config/package.json (content)
⚔️ e2e/vue-start/basic-vue-query/package.json (content)
⚔️ e2e/vue-start/basic/package.json (content)
⚔️ e2e/vue-start/css-modules/package.json (content)
⚔️ e2e/vue-start/custom-basepath/package.json (content)
⚔️ e2e/vue-start/scroll-restoration/package.json (content)
⚔️ e2e/vue-start/selective-ssr/package.json (content)
⚔️ e2e/vue-start/serialization-adapters/package.json (content)
⚔️ e2e/vue-start/server-functions/package.json (content)
⚔️ e2e/vue-start/server-functions/src/routes/abort-signal.tsx (content)
⚔️ e2e/vue-start/server-routes/package.json (content)
⚔️ e2e/vue-start/virtual-routes/package.json (content)
⚔️ e2e/vue-start/website/package.json (content)
⚔️ examples/react/authenticated-routes-firebase/package.json (content)
⚔️ examples/react/authenticated-routes/package.json (content)
⚔️ examples/react/basic-default-search-params/package.json (content)
⚔️ examples/react/basic-devtools-panel/package.json (content)
⚔️ examples/react/basic-file-based/package.json (content)
⚔️ examples/react/basic-non-nested-devtools/package.json (content)
⚔️ examples/react/basic-react-query-file-based/package.json (content)
⚔️ examples/react/basic-react-query/package.json (content)
⚔️ examples/react/basic-ssr-file-based/package.json (content)
⚔️ examples/react/basic-ssr-streaming-file-based/package.json (content)
⚔️ examples/react/basic-virtual-file-based/package.json (content)
⚔️ examples/react/basic-virtual-inside-file-based/package.json (content)
⚔️ examples/react/basic/package.json (content)
⚔️ examples/react/deferred-data/package.json (content)
⚔️ examples/react/i18n-paraglide/package.json (content)
⚔️ examples/react/kitchen-sink-file-based/package.json (content)
⚔️ examples/react/kitchen-sink-react-query-file-based/package.json (content)
⚔️ examples/react/kitchen-sink-react-query/package.json (content)
⚔️ examples/react/kitchen-sink/package.json (content)
⚔️ examples/react/large-file-based/package.json (content)
⚔️ examples/react/location-masking/package.json (content)
⚔️ examples/react/navigation-blocking/package.json (content)
⚔️ examples/react/quickstart-esbuild-file-based/package.json (content)
⚔️ examples/react/quickstart-file-based/package.json (content)
⚔️ examples/react/quickstart-rspack-file-based/package.json (content)
⚔️ examples/react/quickstart-webpack-file-based/package.json (content)
⚔️ examples/react/quickstart/package.json (content)
⚔️ examples/react/router-monorepo-react-query/package.json (content)
⚔️ examples/react/router-monorepo-react-query/packages/app/package.json (content)
⚔️ examples/react/router-monorepo-react-query/packages/router/package.json (content)
⚔️ examples/react/router-monorepo-simple-lazy/package.json (content)
⚔️ examples/react/router-monorepo-simple-lazy/packages/app/package.json (content)
⚔️ examples/react/router-monorepo-simple-lazy/packages/router/package.json (content)
⚔️ examples/react/router-monorepo-simple/package.json (content)
⚔️ examples/react/router-monorepo-simple/packages/app/package.json (content)
⚔️ examples/react/router-monorepo-simple/packages/router/package.json (content)
⚔️ examples/react/scroll-restoration/package.json (content)
⚔️ examples/react/search-validator-adapters/package.json (content)
⚔️ examples/react/start-bare/package.json (content)
⚔️ examples/react/start-basic-auth/package.json (content)
⚔️ examples/react/start-basic-authjs/package.json (content)
⚔️ examples/react/start-basic-cloudflare/package.json (content)
⚔️ examples/react/start-basic-react-query/package.json (content)
⚔️ examples/react/start-basic-rsc/package.json (content)
⚔️ examples/react/start-basic-static/package.json (content)
⚔️ examples/react/start-basic/package.json (content)
⚔️ examples/react/start-bun/package.json (content)
⚔️ examples/react/start-clerk-basic/package.json (content)
⚔️ examples/react/start-convex-trellaux/package.json (content)
⚔️ examples/react/start-counter/package.json (content)
⚔️ examples/react/start-i18n-paraglide/package.json (content)
⚔️ examples/react/start-large/package.json (content)
⚔️ examples/react/start-material-ui/package.json (content)
⚔️ examples/react/start-streaming-data-from-server-functions/package.json (content)
⚔️ examples/react/start-supabase-basic/package.json (content)
⚔️ examples/react/start-tailwind-v4/package.json (content)
⚔️ examples/react/start-trellaux/package.json (content)
⚔️ examples/react/start-workos/package.json (content)
⚔️ examples/react/start-workos/src/components/sign-in-button.tsx (content)
⚔️ examples/react/start-workos/src/routeTree.gen.ts (content)
⚔️ examples/react/start-workos/src/routes/__root.tsx (content)
⚔️ examples/react/start-workos/src/routes/_authenticated.tsx (content)
⚔️ examples/react/start-workos/src/routes/_authenticated/account.tsx (content)
⚔️ examples/react/start-workos/src/routes/api/auth/callback.tsx (content)
⚔️ examples/react/start-workos/src/routes/index.tsx (content)
⚔️ examples/react/start-workos/src/routes/logout.tsx (content)
⚔️ examples/react/start-workos/vite.config.ts (content)
⚔️ examples/react/view-transitions/package.json (content)
⚔️ examples/react/with-framer-motion/package.json (content)
⚔️ examples/react/with-trpc-react-query/package.json (content)
⚔️ examples/react/with-trpc/package.json (content)
⚔️ examples/solid/authenticated-routes-firebase/package.json (content)
⚔️ examples/solid/authenticated-routes/package.json (content)
⚔️ examples/solid/basic-default-search-params/package.json (content)
⚔️ examples/solid/basic-devtools-panel/package.json (content)
⚔️ examples/solid/basic-file-based/package.json (content)
⚔️ examples/solid/basic-non-nested-devtools/package.json (content)
⚔️ examples/solid/basic-solid-query-file-based/package.json (content)
⚔️ examples/solid/basic-solid-query/package.json (content)
⚔️ examples/solid/basic-ssr-file-based/package.json (content)
⚔️ examples/solid/basic-ssr-streaming-file-based/package.json (content)
⚔️ examples/solid/basic-virtual-file-based/package.json (content)
⚔️ examples/solid/basic-virtual-inside-file-based/package.json (content)
⚔️ examples/solid/basic/package.json (content)
⚔️ examples/solid/deferred-data/package.json (content)
⚔️ examples/solid/i18n-paraglide/package.json (content)
⚔️ examples/solid/kitchen-sink-file-based/package.json (content)
⚔️ examples/solid/kitchen-sink-solid-query-file-based/package.json (content)
⚔️ examples/solid/kitchen-sink-solid-query/package.json (content)
⚔️ examples/solid/kitchen-sink/package.json (content)
⚔️ examples/solid/large-file-based/package.json (content)
⚔️ examples/solid/location-masking/package.json (content)
⚔️ examples/solid/navigation-blocking/package.json (content)
⚔️ examples/solid/quickstart-esbuild-file-based/package.json (content)
⚔️ examples/solid/quickstart-file-based/package.json (content)
⚔️ examples/solid/quickstart-rspack-file-based/package.json (content)
⚔️ examples/solid/quickstart-webpack-file-based/package.json (content)
⚔️ examples/solid/quickstart/package.json (content)
⚔️ examples/solid/router-monorepo-simple-lazy/package.json (content)
⚔️ examples/solid/router-monorepo-simple-lazy/packages/app/package.json (content)
⚔️ examples/solid/router-monorepo-simple-lazy/packages/router/package.json (content)
⚔️ examples/solid/router-monorepo-simple/package.json (content)
⚔️ examples/solid/router-monorepo-simple/packages/app/package.json (content)
⚔️ examples/solid/router-monorepo-simple/packages/router/package.json (content)
⚔️ examples/solid/router-monorepo-solid-query/package.json (content)
⚔️ examples/solid/router-monorepo-solid-query/packages/app/package.json (content)
⚔️ examples/solid/router-monorepo-solid-query/packages/router/package.json (content)
⚔️ examples/solid/scroll-restoration/package.json (content)
⚔️ examples/solid/search-validator-adapters/package.json (content)
⚔️ examples/solid/start-basic-auth/package.json (content)
⚔️ examples/solid/start-basic-authjs/package.json (content)
⚔️ examples/solid/start-basic-cloudflare/package.json (content)
⚔️ examples/solid/start-basic-netlify/package.json (content)
⚔️ examples/solid/start-basic-nitro/package.json (content)
⚔️ examples/solid/start-basic-solid-query/package.json (content)
⚔️ examples/solid/start-basic-static/package.json (content)
⚔️ examples/solid/start-basic/package.json (content)
⚔️ examples/solid/start-bun/package.json (content)
⚔️ examples/solid/start-convex-better-auth/package.json (content)
⚔️ examples/solid/start-counter/package.json (content)
⚔️ examples/solid/start-i18n-paraglide/package.json (content)
⚔️ examples/solid/start-large/package.json (content)
⚔️ examples/solid/start-streaming-data-from-server-functions/package.json (content)
⚔️ examples/solid/start-supabase-basic/package.json (content)
⚔️ examples/solid/start-tailwind-v4/package.json (content)
⚔️ examples/solid/view-transitions/package.json (content)
⚔️ examples/solid/with-framer-motion/package.json (content)
⚔️ examples/solid/with-trpc/package.json (content)
⚔️ examples/vue/basic-file-based-jsx/package.json (content)
⚔️ examples/vue/basic-file-based-sfc/package.json (content)
⚔️ examples/vue/basic/package.json (content)
⚔️ package.json (content)
⚔️ packages/arktype-adapter/package.json (content)
⚔️ packages/react-router-devtools/package.json (content)
⚔️ packages/react-router-ssr-query/README.md (content)
⚔️ packages/react-router-ssr-query/package.json (content)
⚔️ packages/react-router/README.md (content)
⚔️ packages/react-router/package.json (content)
⚔️ packages/react-router/src/Asset.tsx (content)
⚔️ packages/react-router/src/index.tsx (content)
⚔️ packages/react-router/src/link.tsx (content)
⚔️ packages/react-router/tests/Scripts.test.tsx (content)
⚔️ packages/react-router/tests/loaders.test.tsx (content)
⚔️ packages/react-router/tests/route.test-d.tsx (content)
⚔️ packages/react-router/tests/routeContext.test.tsx (content)
⚔️ packages/react-router/tests/router.test.tsx (content)
⚔️ packages/react-router/tests/useParams.test.tsx (content)
⚔️ packages/react-start-client/package.json (content)
⚔️ packages/react-start-server/package.json (content)
⚔️ packages/react-start/README.md (content)
⚔️ packages/react-start/package.json (content)
⚔️ packages/router-cli/package.json (content)
⚔️ packages/router-core/package.json (content)
⚔️ packages/router-core/src/index.ts (content)
⚔️ packages/router-core/src/load-matches.ts (content)
⚔️ packages/router-core/src/path.ts (content)
⚔️ packages/router-core/src/redirect.ts (content)
⚔️ packages/router-core/src/route.ts (content)
⚔️ packages/router-core/src/router.ts (content)
⚔️ packages/router-core/src/ssr/createRequestHandler.ts (content)
⚔️ packages/router-core/src/ssr/ssr-client.ts (content)
⚔️ packages/router-core/src/ssr/ssr-server.ts (content)
⚔️ packages/router-core/src/utils.ts (content)
⚔️ packages/router-core/tests/dangerous-protocols.test.ts (content)
⚔️ packages/router-core/tests/getNormalizedURL.test.ts (content)
⚔️ packages/router-core/tests/load.test.ts (content)
⚔️ packages/router-core/tests/utils.test.ts (content)
⚔️ packages/router-devtools-core/package.json (content)
⚔️ packages/router-devtools/package.json (content)
⚔️ packages/router-generator/package.json (content)
⚔️ packages/router-generator/src/generator.ts (content)
⚔️ packages/router-plugin/package.json (content)
⚔️ packages/router-plugin/src/core/code-splitter/compilers.ts (content)
⚔️ packages/router-plugin/src/core/constants.ts (content)
⚔️ packages/router-plugin/src/core/router-code-splitter-plugin.ts (content)
⚔️ packages/router-plugin/tests/code-splitter.test.ts (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/1-default/inline@component.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/1-default/inline@errorComponent.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/1-default/inline@notFoundComponent.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/1-default/useStateDestructure.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/inline@component---errorComponent---notFoundComponent---pendingComponent.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/inline@loader.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/2-components-combined-loader-separate/useStateDestructure.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/inline@component---loader---notFoundComponent---pendingComponent.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/inline@errorComponent.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/snapshots/react/3-all-combined-errorComponent-separate/useStateDestructure.tsx (content)
⚔️ packages/router-plugin/tests/code-splitter/test-files/react/useStateDestructure.tsx (content)
⚔️ packages/router-ssr-query-core/package.json (content)
⚔️ packages/router-utils/package.json (content)
⚔️ packages/router-utils/src/ast.ts (content)
⚔️ packages/router-utils/src/index.ts (content)
⚔️ packages/router-vite-plugin/package.json (content)
⚔️ packages/solid-router-devtools/package.json (content)
⚔️ packages/solid-router-ssr-query/README.md (content)
⚔️ packages/solid-router-ssr-query/package.json (content)
⚔️ packages/solid-router/README.md (content)
⚔️ packages/solid-router/package.json (content)
⚔️ packages/solid-router/src/Asset.tsx (content)
⚔️ packages/solid-router/src/index.tsx (content)
⚔️ packages/solid-router/src/link.tsx (content)
⚔️ packages/solid-router/tests/link.test.tsx (content)
⚔️ packages/solid-router/tests/route.test-d.tsx (content)
⚔️ packages/solid-start-client/package.json (content)
⚔️ packages/solid-start-server/package.json (content)
⚔️ packages/solid-start/README.md (content)
⚔️ packages/solid-start/package.json (content)
⚔️ packages/start-client-core/package.json (content)
⚔️ packages/start-client-core/src/createServerFn.ts (content)
⚔️ packages/start-client-core/src/serverRoute.ts (content)
⚔️ packages/start-client-core/src/tests/createServerFn.test-d.ts (content)
⚔️ packages/start-plugin-core/package.json (content)
⚔️ packages/start-plugin-core/src/prerender.ts (content)
⚔️ packages/start-plugin-core/src/start-compiler-plugin/compiler.ts (content)
⚔️ packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts (content)
⚔️ packages/start-plugin-core/src/start-compiler-plugin/plugin.ts (content)
⚔️ packages/start-plugin-core/tests/compiler.test.ts (content)
⚔️ packages/start-plugin-core/tests/createServerFn/createServerFn.test.ts (content)
⚔️ packages/start-plugin-core/tests/createServerFn/snapshots/server-provider/createServerFnDestructured.tsx (content)
⚔️ packages/start-plugin-core/tests/createServerFn/snapshots/server-provider/createServerFnDestructuredRename.tsx (content)
⚔️ packages/start-plugin-core/tests/createServerFn/snapshots/server-provider/createServerFnStarImport.tsx (content)
⚔️ packages/start-plugin-core/tests/createServerFn/snapshots/server-provider/createServerFnValidator.tsx (content)
⚔️ packages/start-plugin-core/tests/createServerFn/snapshots/server-provider/factory.tsx (content)
⚔️ packages/start-plugin-core/tests/createServerFn/snapshots/server-provider/isomorphic-fns.tsx (content)
⚔️ packages/start-plugin-core/tests/prerender-ssrf.test.ts (content)
⚔️ packages/start-server-core/package.json (content)
⚔️ packages/start-server-core/src/createStartHandler.ts (content)
⚔️ packages/start-server-core/src/index.tsx (content)
⚔️ packages/start-server-core/src/router-manifest.ts (content)
⚔️ packages/start-server-core/src/server-functions-handler.ts (content)
⚔️ packages/start-static-server-functions/package.json (content)
⚔️ packages/start-storage-context/package.json (content)
⚔️ packages/valibot-adapter/package.json (content)
⚔️ packages/vue-router-devtools/package.json (content)
⚔️ packages/vue-router-ssr-query/package.json (content)
⚔️ packages/vue-router/README.md (content)
⚔️ packages/vue-router/package.json (content)
⚔️ packages/vue-router/src/Asset.tsx (content)
⚔️ packages/vue-router/src/index.tsx (content)
⚔️ packages/vue-router/src/link.tsx (content)
⚔️ packages/vue-router/tests/link.test.tsx (content)
⚔️ packages/vue-router/tests/route.test-d.tsx (content)
⚔️ packages/vue-start-client/package.json (content)
⚔️ packages/vue-start-server/package.json (content)
⚔️ packages/vue-start/package.json (content)
⚔️ packages/zod-adapter/package.json (content)
⚔️ pnpm-lock.yaml (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing a customizable allowlist for isDangerousProtocol across multiple router packages.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-router-core-url-protocol-blocklist

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link

nx-cloud bot commented Jan 28, 2026

View your CI Pipeline Execution ↗ for commit 23885c7

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 13m 38s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m View ↗

☁️ Nx Cloud last updated this comment at 2026-02-15 07:03:16 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 28, 2026

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@6542

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@6542

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@6542

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@6542

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@6542

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@6542

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@6542

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@6542

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@6542

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@6542

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@6542

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@6542

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@6542

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@6542

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@6542

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@6542

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@6542

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@6542

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@6542

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@6542

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@6542

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@6542

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@6542

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@6542

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@6542

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@6542

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6542

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@6542

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@6542

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@6542

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@6542

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@6542

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@6542

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6542

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-devtools@6542

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router-ssr-query@6542

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6542

@tanstack/vue-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-client@6542

@tanstack/vue-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start-server@6542

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@6542

commit: 23885c7

@github-actions github-actions bot added the documentation Everything documentation related label Jan 28, 2026
Comment on lines -127 to -137
// Block dangerous protocols in redirect href
if (
!opts._builtLocation &&
typeof opts.href === 'string' &&
isDangerousProtocol(opts.href)
) {
throw new Error(
`Redirect blocked: unsafe protocol in href "${opts.href}". Only ${SAFE_URL_PROTOCOLS.join(', ')} protocols are allowed.`,
)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to check here, since a redirect() is always handled by something else that will check, right?

@hiendv
Copy link
Contributor

hiendv commented Feb 12, 2026

I believe I have the same problem as yours. x-safari-https stops working for us after the update.
But from my POV, the whitelist works better than the blacklist. The current default whitelist is reasonable and works with most cases. We just need a way to config additional allowed protocols.

@hiendv hiendv mentioned this pull request Feb 12, 2026
- When `true`, disables the global catch boundary that normally wraps all route matches. This allows unhandled errors to bubble up to top-level error handlers in the browser.
- Useful for testing tools, error reporting services, and debugging scenarios.

### `protocolBlocklist` property
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we better stick with a sensible default with whitelist
e.g. ['http:', 'https:', 'mailto:', 'tel:'

And allow users to specify protocolWhitelist or protocolAllowList


- Type: `Array<string>`
- Optional
- Defaults to `DEFAULT_PROTOCOL_BLOCKLIST` which includes:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we wouldn't need DEFAULT_PROTOCOL_BLOCKLIST

Copy link
Contributor

@hiendv hiendv Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test case for <Link />?

Something like this

describe('weird links', () => {
  test('should work', async () => {
    const rootRoute = createRootRoute()
    const inputs = [
      'x-safari-https://example.com',
      'googlechromes://example.com',
      'intent://example.com#Intent;scheme=https;end',
    ]
    const indexRoute = createRoute({
      getParentRoute: () => rootRoute,
      path: '/',
      component: () => {
        return inputs.map((input) => <Link to={input} key={input} />)
      },
    })

    const router = createRouter({
      routeTree: rootRoute.addChildren([indexRoute]),
      history,
    })

    render(<RouterProvider router={router} />)

    const links = await screen.findAllByRole('link')
    const hrefs = links.map((el) => el.getAttribute('href'))
    expect(hrefs).toBe(inputs)
  })
})

const reloadHref = !hrefIsUrl && publicHref ? publicHref : href

// Block dangerous protocols like javascript:, data:, vbscript:
// Block dangerous protocols like javascript:, blob:, data:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to change this?

* move to allowlist

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
@Sheraff Sheraff changed the title feat(router-core, react-router, solid-router, vue-router): isDangerousProtocol uses customizable blocklist feat(router-core, react-router, solid-router, vue-router): isDangerousProtocol uses customizable allowlist Feb 14, 2026
Copy link
Contributor Author

@Sheraff Sheraff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove :sms protocol from defaults

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/router-core/src/router.ts`:
- Around line 1045-1046: The allowlist initialization uses
this.options.protocolAllowlist directly, causing mismatches because URL.protocol
returns lowercase values with a trailing colon; update the assignment of
this.protocolAllowlist so it normalizes each entry from
this.options.protocolAllowlist to lowercase and ensures a trailing ':' (e.g.,
map entries with entry = entry.toLowerCase() and append ':' if missing) before
creating the Set, so checks against URL.protocol will match custom schemes like
'x-safari-https:' consistently.
🧹 Nitpick comments (2)
packages/router-core/src/utils.ts (1)

538-550: Consider freezing the exported allowlist to prevent accidental mutation.

Since DEFAULT_PROTOCOL_ALLOWLIST is exported, a consumer mutating it would silently change defaults globally. Freezing it keeps the default truly constant.

🧊 Suggested tweak
-export const DEFAULT_PROTOCOL_ALLOWLIST = [
+export const DEFAULT_PROTOCOL_ALLOWLIST = Object.freeze([
   // Standard web navigation
   'http:',
   'https:',

   // Common browser-safe actions
   'mailto:',
   'tel:',
-]
+])
packages/router-core/src/router.ts (1)

474-482: Clarify protocol formatting requirements in docs.

To reduce config footguns, explicitly mention that entries should be lowercase and include the trailing : (e.g., x-safari-https:). This helps avoid silent mismatches.

Comment on lines +1045 to +1046
this.protocolAllowlist = new Set(this.options.protocolAllowlist)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize allowlist entries to avoid false blocks for custom protocols.

If callers pass x-safari-https (no colon) or uppercase variants, the URL parser will produce x-safari-https: in lowercase and the current Set won’t match. Normalizing once here prevents surprising blocks for valid custom schemes.

✅ Suggested normalization
-    this.protocolAllowlist = new Set(this.options.protocolAllowlist)
+    this.protocolAllowlist = new Set(
+      this.options.protocolAllowlist.map((p) => {
+        const normalized = p.toLowerCase()
+        return normalized.endsWith(':') ? normalized : `${normalized}:`
+      }),
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.protocolAllowlist = new Set(this.options.protocolAllowlist)
this.protocolAllowlist = new Set(
this.options.protocolAllowlist.map((p) => {
const normalized = p.toLowerCase()
return normalized.endsWith(':') ? normalized : `${normalized}:`
}),
)
🤖 Prompt for AI Agents
In `@packages/router-core/src/router.ts` around lines 1045 - 1046, The allowlist
initialization uses this.options.protocolAllowlist directly, causing mismatches
because URL.protocol returns lowercase values with a trailing colon; update the
assignment of this.protocolAllowlist so it normalizes each entry from
this.options.protocolAllowlist to lowercase and ensures a trailing ':' (e.g.,
map entries with entry = entry.toLowerCase() and append ':' if missing) before
creating the Set, so checks against URL.protocol will match custom schemes like
'x-safari-https:' consistently.

@Sheraff Sheraff merged commit 6ddb586 into main Feb 15, 2026
6 checks passed
Sheraff added a commit that referenced this pull request Feb 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants