Redirect old hash links and guard register code in site.js #6

Merged
Claude merged 3 commits from cc-2-hash-redirects into site-hardening 2026-10-07 13:41:44 +00:00
Collaborator

Most of CC-2 landed with the page split. This adds the last bit: old /#/how, /#/spec/hub style links now redirect to /how, /spec/hub from site.js. The script tag moves into the footer partial so every page loads it, including / where old links land. The register form code only runs when the form exists.

Spec pages live at /spec/<id>, not the /nodes/<id> in the ticket. No redirect is needed for /nodes/<id> because it was never a URL.

This touches the same lines of site.js as PR #3 (CC-3), so whichever merges second needs a small rebase. The build test now asserts every page includes site.js. node --test passes.

Most of CC-2 landed with the page split. This adds the last bit: old `/#/how`, `/#/spec/hub` style links now redirect to `/how`, `/spec/hub` from `site.js`. The script tag moves into the footer partial so every page loads it, including `/` where old links land. The register form code only runs when the form exists. Spec pages live at `/spec/<id>`, not the `/nodes/<id>` in the ticket. No redirect is needed for `/nodes/<id>` because it was never a URL. This touches the same lines of `site.js` as PR #3 (CC-3), so whichever merges second needs a small rebase. The build test now asserts every page includes `site.js`. `node --test` passes.
Claude changed target branch from main to site-hardening 2026-10-07 11:09:54 +00:00
rob force-pushed cc-2-hash-redirects from 307fd71b5e to af5acbd25d
All checks were successful
CI / check (pull_request) Successful in 9s
2026-10-07 13:34:32 +00:00
Compare
Claude left a comment

Two findings.

Two findings.
site.js Outdated
@ -1,31 +1,35 @@
if (location.hash.startsWith('#/')) location.replace('/' + location.hash.slice(2));
Author
Collaborator

Open redirect. /#//evil.example/x gives location.replace('//evil.example/x'), a protocol-relative URL, so the site bounces visitors to another origin. /#/\evil.example is the same on browsers that treat backslash as slash. Match a safe shape instead, e.g. const [, path] = location.hash.match(/^#\/([\w-]+(?:\/[\w-]+)*)?$/) || []; if (path !== undefined) location.replace('/' + path);. Or at least reject a second leading slash or backslash.

Open redirect. /#//evil.example/x gives location.replace('//evil.example/x'), a protocol-relative URL, so the site bounces visitors to another origin. /#/\evil.example is the same on browsers that treat backslash as slash. Match a safe shape instead, e.g. `const [, path] = location.hash.match(/^#\/([\w-]+(?:\/[\w-]+)*)?$/) || []; if (path !== undefined) location.replace('/' + path);`. Or at least reject a second leading slash or backslash.
@ -13,6 +13,7 @@ test('every page builds with one header, and register has no band', () => {
const html = fs.readFileSync(path.join(root, 'dist', `${page}.html`), 'utf8');
assert.strictEqual(html.split('class="site-header"').length - 1, 1, page);
assert.ok(!html.includes(' style='), page);
assert.strictEqual(html.split('<script src="/site.js"></script>').length - 1, 1, page);
Author
Collaborator

The redirect has no test, only the script tag count. Reverting the redirect line would still pass. Add a test that runs site.js (node:vm with a stub location) for #/how, #/spec/hub, no hash, and #//evil.example (must not redirect off-origin).

The redirect has no test, only the script tag count. Reverting the redirect line would still pass. Add a test that runs site.js (node:vm with a stub location) for #/how, #/spec/hub, no hash, and #//evil.example (must not redirect off-origin).
Restrict hash redirects to safe paths and test them
All checks were successful
CI / check (pull_request) Successful in 11s
a540ee95fc
Claude merged commit 91102a83c9 into site-hardening 2026-10-07 13:41:44 +00:00
Claude deleted branch cc-2-hash-redirects 2026-10-07 13:41:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rob/CamperCan!6
No description provided.