Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions tests/site-archive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,22 @@ test("actual tar retains both the previous entry and its lazy imports", async t
await check(await archive(t, members()));
});

test("v2 accepts only changing Cloudflare JSD request metadata while preserving all HTML and script checks", async t => {
const jsd = (ray: string, time: string) => `<script>var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'${ray}',t:'${time}'};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";</script>`;
const captured = shell(oldName).replace("</body>", jsd("a4942470f8500900", "MTc5MTc4NzUzMg==") + "</body>");
const active = captured.replace("a4942470f8500900", "a49426f59e37cb83").replace("MTc5MTc4NzUzMg==", "MTc5MTc4NzYzNg==");
for (const capturedWrapper of ["legacy", "parent-location"]) for (const activeWrapper of ["legacy", "parent-location"]) test(`v2 accepts known Cloudflare JSD wrappers (${capturedWrapper} to ${activeWrapper})`, async t => {
const continuation = (wrapper: string) => wrapper === "legacy" ? "var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);"
: "var l=window.parent.location,o;try{o=new URL(l.origin).origin}catch(e){try{o=new URL(new URL(l.href).origin).origin}catch(e){o=''}}var u='/cdn-cgi/challenge-platform/scripts/jsd/main.js?ray=REQUEST_RAY',a=document.createElement('script');a.src=o?new URL(u,o).href:u;document.getElementsByTagName('head')[0].appendChild(a);";
const jsd = (wrapper: string, ray: string, time: string) => `<script>var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'${ray}',t:'${time}'};${continuation(wrapper).replace("REQUEST_RAY", ray)}";</script>`;
const captured = shell(oldName).replace("</body>", jsd(capturedWrapper, "a4942470f8500900", "MTc5MTc4NzUzMg==") + "</body>");
const active = shell(oldName).replace("</body>", jsd(activeWrapper, "a49426f59e37cb83", "MTc5MTc4NzYzNg==") + "</body>");
const probe = previousShellProbe(captured, new URL(origin));
const entries = members().map(item => item.name.endsWith("previous-shell.html") ? { ...item, text:probe }
: item.name.endsWith("previous-shell.json") ? { ...item, text:JSON.stringify({version:2,capturedAt:now,sourcePath:"/next/relay-forge/",sourceSha256:sha(normalizePublicShell(captured)),probeSha256:sha(probe)}) } : item);
const tar = await archive(t, entries);
const serve = (html: string): typeof fetch => async (input, options) => String(input).endsWith("retained-releases.json") ? server()(input, options)
: new Response(html, {headers:{"content-type":"text/html"}});
await check(tar, serve(active));
for (const altered of [active.replace("jsd/main.js", "jsd/altered.js"), active.replace("</head>", "<script>malicious()</script></head>"), active.replace(oldName, newName), active.replace("MTc5MTc4NzYzNg==", "YWxlcnQoMSk=")]) {
const alterations = [active.replace("jsd/main.js", "jsd/altered.js"), active.replace("</head>", "<script>malicious()</script></head>"), active.replace(oldName, newName), active.replace("MTc5MTc4NzYzNg==", "YWxlcnQoMSk="), active.replace("document.getElementsByTagName('head')[0].appendChild(a);", "document.body.appendChild(a);")];
if (activeWrapper === "parent-location") alterations.push(active.replace("?ray=a49426f59e37cb83", "?ray=invalid"));
for (const altered of alterations) {
await assert.rejects(check(tar, serve(altered)), /Previous-shell/);
}
const legacy = entries.map(item => item.name.endsWith("previous-shell.json") ? {...item,text:JSON.stringify({version:1,capturedAt:now,sourcePath:"/next/relay-forge/",sourceSha256:sha(captured),probeSha256:sha(probe)})}:item);
Expand Down
3 changes: 2 additions & 1 deletion tools/check-site-archive.mts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ export async function assertSiteArchiveSafe(options: {
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
assertSiteArchiveSafe({ archive: process.argv[2] || "", siteUrl: process.argv[3] || "" }).then(() => {
console.log("Deployment archive retention verified.");
}).catch(() => {
}).catch((error: unknown) => {
console.error("Archive verification failed; rebuild with current retained assets before uploading.");
console.error(error instanceof Error ? error.message : "Unknown archive verification error.");
process.exitCode = 1;
});
}
11 changes: 8 additions & 3 deletions tools/retain-site-assets.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ export const ASSET_RETENTION_MS = 48 * 60 * 60 * 1000;
export const PREVIOUS_SHELL_PATH = "/deployment-check/previous-shell.html";
export const PREVIOUS_SHELL_META_PATH = "/deployment-check/previous-shell.json";
export function normalizePublicShell(html: string): string {
// Compare only this known Cloudflare JSD request metadata; keep its code and the stored probe intact.
return html.replace(/(d\.innerHTML="window\.__CF\$cv\$params=\{r:')[a-f0-9]{16}(',t:')([A-Za-z0-9+/]+={0,2})('\};var a=document\.createElement\('script'\);a\.src='\/cdn-cgi\/challenge-platform\/scripts\/jsd\/main\.js';)/g,
(match, before: string, between: string, time: string, after: string) => /^\d{10}$/.test(Buffer.from(time, "base64").toString("utf8")) ? `${before}REQUEST_RAY${between}REQUEST_TIME${after}` : match);
// Cloudflare alternates these two exact loaders per request; any other HTML/code change must still fail comparison.
const prefix = "d.innerHTML=\"window.__CF$cv$params={r:'REQUEST_RAY',t:'REQUEST_TIME'};";
const legacy = "var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";
const parent = "var l=window.parent.location,o;try{o=new URL(l.origin).origin}catch(e){try{o=new URL(new URL(l.href).origin).origin}catch(e){o=''}}var u='/cdn-cgi/challenge-platform/scripts/jsd/main.js?ray=REQUEST_RAY',a=document.createElement('script');a.src=o?new URL(u,o).href:u;document.getElementsByTagName('head')[0].appendChild(a);";
return html.replace(/(d\.innerHTML="window\.__CF\$cv\$params=\{r:')[a-f0-9]{16}(',t:')([A-Za-z0-9+/]+={0,2})('\};(?:var a=document\.createElement\('script'\);a\.src='\/cdn-cgi\/challenge-platform\/scripts\/jsd\/main\.js';|var l=window\.parent\.location,o;))/g,
(match, before: string, between: string, time: string, after: string) => /^\d{10}$/.test(Buffer.from(time, "base64").toString("utf8")) ? `${before}REQUEST_RAY${between}REQUEST_TIME${after}` : match)
.replace(/(var u='\/cdn-cgi\/challenge-platform\/scripts\/jsd\/main\.js\?ray=)[a-f0-9]{16}(',a=document\.createElement\('script'\);)/g, "$1REQUEST_RAY$2")
.replaceAll(prefix + parent + '"', prefix + legacy + '"');
}
export function previousShellProbe(html: string, origin: URL): string {
if (!/<head(?:\s[^>]*)?>/i.test(html) || /<base\b/i.test(html)) throw new Error("Previous shell needs a verifiable head without base overrides.");
Expand Down
Loading