Files
workinf_Blender_Wasm/tools/web/check-csp-resource-policy.mjs
mes123456 380cbed4ff
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

115 lines
8.3 KiB
JavaScript

import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { createDeploymentHttpServer, listenDeploymentHttpServer, loadDeploymentContract } from "./deployment-http-server.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const reportPath = path.join(root, "tests/golden/M13-05B/csp-resource-report.json");
const manifestPath = path.join(root, "tests/golden/M13-05B/manifest.json");
const hashFile = async (file) => crypto.createHash("sha256").update(await fs.promises.readFile(file)).digest("hex");
const contract = loadDeploymentContract();
const policy = contract.responseHeaders.allResponses["Content-Security-Policy"];
const directives = new Map(policy.split(";").map((directive) => {
const tokens = directive.trim().split(/\s+/u);
return [tokens.shift(), tokens];
}));
const tokens = (name) => directives.get(name) ?? [];
const sameOriginOnly = ["worker-src", "font-src", "img-src", "media-src"];
assert.deepEqual(tokens("script-src"), ["'self'", "'wasm-unsafe-eval'"]);
assert.deepEqual(tokens("worker-src"), ["'self'"]);
assert.deepEqual(tokens("font-src"), ["'self'"]);
assert.deepEqual(tokens("img-src"), ["'self'"]);
assert.deepEqual(tokens("media-src"), ["'self'"]);
for (const name of sameOriginOnly) for (const forbidden of ["data:", "blob:", "*"]) assert.equal(tokens(name).includes(forbidden), false, `${name} contains ${forbidden}`);
for (const forbidden of ["'unsafe-inline'", "'unsafe-eval'", "data:", "blob:", "*"]) {
assert.equal([...directives.values()].flat().includes(forbidden), false, `CSP contains forbidden token ${forbidden}`);
}
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "m13-05b-csp-"));
const onePixelPng = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=", "base64");
const wav = Buffer.alloc(48);
wav.write("RIFF", 0); wav.writeUInt32LE(40, 4); wav.write("WAVEfmt ", 8); wav.writeUInt32LE(16, 16); wav.writeUInt16LE(1, 20); wav.writeUInt16LE(1, 22); wav.writeUInt32LE(8000, 24); wav.writeUInt32LE(8000, 28); wav.writeUInt16LE(1, 32); wav.writeUInt16LE(8, 34); wav.write("data", 36); wav.writeUInt32LE(4, 40); wav.fill(128, 44);
const fontSource = path.join(root, "blender/release/datafiles/fonts/Inter.woff2");
assert.ok(fs.existsSync(fontSource), "font fixture is missing");
const write = (relative, value) => { const file = path.join(fixtureRoot, relative); fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, value); };
write("index.html", "<!doctype html><title>CSP resource matrix</title><script type=module src=/app.js></script>");
write("app.js", `
const result = { worker: false, wasm: false, image: false, font: false, media: false, dataImageBlocked: false, blobWorkerBlocked: false, crossOriginBlocked: false };
const violations = [];
document.addEventListener("securitypolicyviolation", (event) => {
violations.push({ directive: event.effectiveDirective, blockedURI: event.blockedURI });
if (event.effectiveDirective === "worker-src" && (event.blockedURI === "blob" || event.blockedURI.startsWith("blob:"))) result.blobWorkerBlocked = true;
if (event.effectiveDirective === "connect-src") result.crossOriginBlocked = true;
});
const worker = new Worker("/worker.js", { type: "module" });
worker.onmessage = () => { result.worker = true; worker.terminate(); };
fetch("/engine.wasm").then((response) => WebAssembly.instantiateStreaming(response)).then(() => { result.wasm = true; }).catch(() => {});
const image = new Image(); image.onload = () => { result.image = true; }; image.src = "/pixel.png";
const audio = new Audio(); audio.addEventListener("loadstart", () => { result.media = true; }, { once: true }); audio.src = "/tone.wav"; audio.load();
new FontFace("CSPMatrix", "url(/font.woff2)").load().then(() => { result.font = true; }).catch(() => {});
const blockedImage = new Image(); blockedImage.onerror = () => { result.dataImageBlocked = true; }; blockedImage.src = "data:image/png;base64,iVBORw0KGgo=";
try { new Worker(URL.createObjectURL(new Blob(["postMessage('unexpected')"], { type: "text/javascript" }))); } catch { result.blobWorkerBlocked = true; }
fetch("http://127.0.0.1:9/csp-cross-origin").catch(() => { result.crossOriginBlocked = true; });
setTimeout(() => { window.__cspResourceResult = { result, violations }; }, 500);
`);
write("worker.js", "postMessage('worker-ok');\n");
write("engine.wasm", Buffer.from([0, 97, 115, 109, 1, 0, 0, 0]));
write("pixel.png", onePixelPng);
write("tone.wav", wav);
fs.copyFileSync(fontSource, path.join(fixtureRoot, "font.woff2"));
const server = createDeploymentHttpServer({ root: fixtureRoot, contract });
const origin = await listenDeploymentHttpServer(server);
const expected = new Map([
["/worker.js", ".js"], ["/engine.wasm", ".wasm"], ["/font.woff2", ".woff2"], ["/pixel.png", ".png"], ["/tone.wav", ".wav"],
]);
try {
for (const [pathname, extension] of expected) {
const response = await fetch(`${origin}${pathname}`);
assert.equal(response.status, 200, pathname);
assert.equal(response.headers.get("content-security-policy"), policy, `CSP mismatch ${pathname}`);
assert.equal(response.headers.get("content-type"), contract.mimeTypes[extension], `MIME mismatch ${pathname}`);
assert.ok(Number(response.headers.get("content-length")) > 0, `empty resource ${pathname}`);
}
for (const pathname of ["/missing", "/index.html"]) {
const response = await fetch(`${origin}${pathname}`);
assert.equal(response.headers.get("content-security-policy"), policy, `CSP missing on ${pathname}`);
}
const { chromium } = await import(pathToFileURL(path.join(root, "web/node_modules/playwright/index.mjs")).href);
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(`${origin}/`, { waitUntil: "networkidle" });
await page.waitForTimeout(700);
const browserReport = await page.evaluate(() => window.__cspResourceResult);
await browser.close();
assert.ok(browserReport, "browser CSP report missing");
assert.equal(browserReport.result.worker, true);
assert.equal(browserReport.result.wasm, true);
assert.equal(browserReport.result.image, true);
assert.equal(browserReport.result.media, true);
assert.equal(browserReport.result.dataImageBlocked, true);
assert.equal(browserReport.result.blobWorkerBlocked, true);
assert.equal(browserReport.result.crossOriginBlocked, true);
assert.ok(browserReport.violations.some(({ directive }) => directive === "img-src"));
assert.ok(browserReport.violations.some(({ directive }) => directive === "worker-src"));
assert.ok(browserReport.violations.some(({ directive }) => directive === "connect-src"));
const report = {
schemaVersion: 1, task: "M13-05B", operation: "CSP_RESOURCE_POLICY", policy,
resources: { worker: "SAME_ORIGIN", wasm: "SAME_ORIGIN_WASM_UNSAFE_EVAL", font: "SAME_ORIGIN", image: "SAME_ORIGIN", media: "SAME_ORIGIN" },
denied: { dataImage: "DENY", blobWorker: "DENY", crossOriginConnect: "DENY" },
responseCount: expected.size + 2, browser: browserReport, execution: "DISABLED", nextTask: "M13-05C",
};
if (process.env.UPDATE_M13_05B_REPORT === "1") { await fs.promises.mkdir(path.dirname(reportPath), { recursive: true }); await fs.promises.writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`); }
assert.deepEqual(JSON.parse(await fs.promises.readFile(reportPath, "utf8")), report);
const manifest = JSON.parse(await fs.promises.readFile(manifestPath, "utf8"));
assert.deepEqual({ schemaVersion: manifest.schemaVersion, task: manifest.task, parentTask: manifest.parentTask, nextTask: manifest.nextTask }, { schemaVersion: 1, task: "M13-05B", parentTask: "M13-05A", nextTask: "M13-05C" });
for (const artifact of Object.values(manifest.artifacts)) assert.equal(await hashFile(path.join(root, artifact.path)), artifact.sha256, `artifact hash mismatch ${artifact.path}`);
process.stdout.write("csp-resource-policy-ok worker=SAME_ORIGIN wasm=SAME_ORIGIN font=SAME_ORIGIN image=SAME_ORIGIN media=SAME_ORIGIN denied=data,blob,cross-origin execution=DISABLED next=M13-05C\n");
} finally {
if (server.listening) await new Promise((resolve) => server.close(resolve));
fs.rmSync(fixtureRoot, { recursive: true, force: true });
}