Skip to content
Merged
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,10 @@ permanently 301-redirects every `www.seofixkit.com` request onto the apex host
with the path and query intact. Every URL the Worker serves (page canonicals,
social tags, `robots.txt`, `sitemap.xml`, `llms.txt`, and fixture URLs) is
generated from the apex origin, so canonicals and the sitemap are apex-only.
The assets binding runs the Worker first for every request (`run_worker_first:
true`), so static assets (`/assets/*`, `/favicon.svg`, `/security.txt`) are
redirected off `www` as well instead of being served straight from the asset
CDN on a second host.

## Product boundary

Expand Down
14 changes: 14 additions & 0 deletions scripts/live-promise-spot-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,20 @@ export function canonicalHostSpotChecks(baseUrl) {
reason: "redirect preserves the path and query"
}
]
},
{
path: "www.seofixkit.com/favicon.svg",
name: "www.seofixkit.com static assets redirect too (no asset-host leakage)",
url: `${wwwOrigin}/favicon.svg`,
redirectManual: true,
acceptStatuses: [301],
expectedHeaders: [
{
name: "location",
value: `${baseUrl}/favicon.svg`,
reason: "static asset path must 301 onto the apex host"
}
]
}
];
}
Expand Down
4 changes: 2 additions & 2 deletions scripts/live-promise-spot-check.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ test("live spot-check covers the public machine surfaces", () => {
test("live spot-check covers the www-to-apex canonical redirect", () => {
assert.deepEqual(
canonicalHostSpotChecks(origin).map((check) => check.path),
["www.seofixkit.com/", "www.seofixkit.com/check"]
["www.seofixkit.com/", "www.seofixkit.com/check", "www.seofixkit.com/favicon.svg"]
);
for (const check of canonicalHostSpotChecks(origin)) {
assert.equal(check.redirectManual, true, "redirect checks must observe the 301 itself");
Expand All @@ -101,7 +101,7 @@ test("live spot-check covers the www-to-apex canonical redirect", () => {

test("live spot-check passes against the shipped public page copy", async () => {
const results = await spotCheckPublicPages({ baseUrl: origin, fetcher: pageFetcher() });
assert.equal(results.length, 16);
assert.equal(results.length, 17);
for (const result of results) {
assert.deepEqual(result.failures, [], `${result.path} must pass: ${result.name}`);
}
Expand Down
2 changes: 1 addition & 1 deletion shared/promise-audit.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ test("README anonymous one-page check claim matches the Worker, page, and rate l
assert.match(publicCheckSource, /opens the page in a real browser|open the page in a real browser/i, "the public check copy states real browser rendering");
assert.match(publicCheckSource, /check:target-day/, "the check is rate-limited per site");
assert.match(publicCheckSource, /does not guarantee rankings, traffic, indexing, revenue, AI citations/i, "the public check keeps the no-ranking boundary");
assert.match(wranglerJsonc, /"\/check"/, "/check is served by the Worker before SPA assets");
assert.match(wranglerJsonc, /"run_worker_first":\s*true/, "/check (and every other path) is served by the Worker before SPA assets");
});

test("README homepage anonymous-check claim matches the CTA beside the email form", () => {
Expand Down
9 changes: 9 additions & 0 deletions worker/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,15 @@ test("Worker dispatch 301-redirects www.seofixkit.com onto the apex host", async
const sitemap = await worker.fetch(new Request("https://www.seofixkit.com/sitemap.xml"), env, fakeCtx());
assert.equal(sitemap.status, 301);
assert.equal(sitemap.headers.get("location"), "https://seofixkit.com/sitemap.xml");

// Static assets must 301 too: with wrangler.jsonc "run_worker_first": true
// every www request reaches this Worker (the array form let asset paths
// bypass the Worker and be served 200 straight from the asset CDN).
for (const assetPath of ["/favicon.svg", "/security.txt", "/assets/index-abc123.css"]) {
const asset = await worker.fetch(new Request(`https://www.seofixkit.com${assetPath}`), env, fakeCtx());
assert.equal(asset.status, 301, `www ${assetPath} must 301`);
assert.equal(asset.headers.get("location"), `https://seofixkit.com${assetPath}`);
}
});

test("Worker dispatch serves apex-only canonicals, robots, sitemap, and llms.txt", async () => {
Expand Down
21 changes: 17 additions & 4 deletions worker/routes/pages.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,24 @@ test("static public skill and sitemap files keep buyer-facing boundaries", () =>
});

test("Cloudflare asset routing sends public proof pages through the Worker", () => {
const wrangler = JSON.parse(readFileSync(new URL("../../wrangler.jsonc", import.meta.url), "utf8"));
const runWorkerFirst = new Set(wrangler.assets?.run_worker_first || []);
const jsonc = readFileSync(new URL("../../wrangler.jsonc", import.meta.url), "utf8");
const wrangler = JSON.parse(jsonc.replace(/^\s*\/\/.*$/gm, ""));
const runWorkerFirst = wrangler.assets?.run_worker_first;

for (const path of ["/demo", "/methodology", "/packages", "/check", "/support", "/terms", "/privacy"]) {
assert.equal(runWorkerFirst.has(path), true, `${path} must be served by the Worker before SPA assets`);
// Boolean true runs the Worker before asset serving for every request, which
// is what keeps www.seofixkit.com asset paths 301-ing onto the apex host. An
// array must at least cover every public proof page; a missing or false
// value would let assets bypass the Worker and be served from the www host.
assert.equal(
runWorkerFirst === true || Array.isArray(runWorkerFirst),
true,
Comment on lines +342 to +344

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require boolean worker-first routing in the regression test

Require runWorkerFirst === true here. The previous path-array configuration still satisfies this assertion and the subsequent page checks, yet it lets /favicon.svg and /assets/* bypass the Worker on www, recreating the exact canonical-host regression this commit addresses. The live spot-check unit test uses a mocked redirect and the real live audit is opt-in, so the normal offline CI suite would not catch such a configuration rollback.

Useful? React with 👍 / 👎.

"run_worker_first must be true or cover the public proof pages"
);
if (Array.isArray(runWorkerFirst)) {
const covered = new Set(runWorkerFirst);
for (const path of ["/demo", "/methodology", "/packages", "/check", "/support", "/terms", "/privacy"]) {
assert.equal(covered.has(path), true, `${path} must be served by the Worker before SPA assets`);
}
}
});

Expand Down
8 changes: 7 additions & 1 deletion wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@
"directory": "./dist",
"binding": "ASSETS",
"not_found_handling": "404-page",
"run_worker_first": ["/", "/api/*", "/v1/*", "/admin/*", "/beta", "/beta/*", "/demo", "/methodology", "/packages", "/check", "/r/*", "/.well-known/seofixkit-report-domain.txt", "/llms.txt", "/privacy", "/support", "/terms", "/robots.txt", "/sitemap.xml", "/fixture/*"]
// Every request must run the Worker first (not just the listed routes) so

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve strict-JSON compatibility for the audit runner

These newly added comments make the file invalid JSON, but readDodoPublicConfig() in scripts/run-live-audit-batch.mjs:534 still reads it with JSON.parse. When the documented Fix Pack webhook drill runs without both public Dodo IDs duplicated in environment variables, it falls back to this file and now throws a SyntaxError before constructing the drill event. Keep this config strict JSON or update that reader to parse JSONC.

Useful? React with 👍 / 👎.

// the www.seofixkit.com -> https://seofixkit.com 301 in worker/index.js also
// covers static assets (/assets/*, /favicon.svg, /security.txt, ...). With a
// path list, asset requests bypass the Worker and www serves them 200, which
// leaks a second host into canonicals/sitemap indexing. The Worker's final
// ASSETS.fetch fallthrough keeps apex serving identical bytes for all paths.
"run_worker_first": true
},
"r2_buckets": [
{
Expand Down
Loading