From c3a0da184126f82284baeb9b16e8220f22eeb39c Mon Sep 17 00:00:00 2001 From: DevCats Date: Thu, 25 Jun 2026 19:53:36 +0000 Subject: [PATCH 1/2] fix(site): restore SPA path before router boots so 404 page renders Move the GitHub Pages deep-link restoration step from main.tsx into an inline + // GitHub Pages SPA fallback: capture the original path in a query // parameter and bounce to the SPA index, which restores it before - // React Router boots. See site/src/main.tsx for the restore step. + // React Router boots. See site/index.html for the restore step. // // `%BASE_URL%` is substituted at build time by the // `rewrite-public-base-url` Vite plugin (see site/vite.config.ts), diff --git a/site/src/main.tsx b/site/src/main.tsx index 8a71460..3eb3b35 100644 --- a/site/src/main.tsx +++ b/site/src/main.tsx @@ -2,13 +2,8 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { App } from "./App"; -// Restore SPA path captured by public/404.html (GitHub Pages deep-link trick). -const url = new URL(window.location.href); -const original = url.searchParams.get("p"); -if (original) { - url.searchParams.delete("p"); - window.history.replaceState(null, "", original + url.search + url.hash); -} +// Note: the `?p=...` query param emitted by public/404.html is restored +// by the inline script in index.html so the router sees the original path. const appRoot = document.getElementById("root"); if (appRoot === null) { diff --git a/site/src/pages/NotFoundPage.tsx b/site/src/pages/NotFoundPage.tsx index abf0891..7f53ac6 100644 --- a/site/src/pages/NotFoundPage.tsx +++ b/site/src/pages/NotFoundPage.tsx @@ -1,27 +1,48 @@ import type { FC } from "react"; -import { Link } from "react-router-dom"; +import { Link, useLocation } from "react-router-dom"; import { usePageTitle } from "../lib/usePageTitle"; export const NotFoundPage: FC = () => { + const { pathname } = useLocation(); usePageTitle("not found"); return ( -
-
+
+
404
-

- That path doesn't exist in this report. -

-
+
+

+ That page isn't part of this scan report. +

+

+ We couldn't find{" "} + + {pathname} + + . It may have moved, been renamed, or never existed. +

+
+
- Back to the latest scan + Latest scan - - or browse the scan history + + Scan history + + Raw JSON + + + Source +
); From df19bfe07b2c8b8b81d2bc5278f501ce06c2895b Mon Sep 17 00:00:00 2001 From: DevCats Date: Thu, 25 Jun 2026 20:44:28 +0000 Subject: [PATCH 2/2] fix(site): parse the bounce `?p=` value through URL before restoring Build the restored URL by feeding `p` into the URL constructor and appending any extra search params from the bounce URL. The old version concatenated strings, which produced a malformed `/foo?a=1?x=2` if `p` already carried a query (or an extra param landed on the bounce URL). Also reject `p` values that don't look like same-origin relative paths so a crafted `?p=//host/foo` can't try to flip the visible origin. Addresses Copilot feedback on #15. --- site/index.html | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/site/index.html b/site/index.html index 9feb80c..2e4e91b 100644 --- a/site/index.html +++ b/site/index.html @@ -16,10 +16,22 @@ (function () { var url = new URL(window.location.href); var p = url.searchParams.get("p"); - if (p) { - url.searchParams.delete("p"); - window.history.replaceState(null, "", p + url.search + url.hash); - } + // Only restore same-origin relative paths so a crafted ?p= can't try + // to replace the visible origin (replaceState would throw anyway). + if (!p || p[0] !== "/" || p[1] === "/") return; + url.searchParams.delete("p"); + // Parse `p` through URL so its own query/hash compose cleanly with + // whatever extra params or hash were on the bounce URL. + var restored = new URL(p, window.location.origin); + url.searchParams.forEach(function (value, key) { + restored.searchParams.append(key, value); + }); + if (!restored.hash) restored.hash = url.hash; + window.history.replaceState( + null, + "", + restored.pathname + restored.search + restored.hash, + ); })();