Files
workinf_Blender_Wasm/tools/web/check-csp-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

82 lines
4.9 KiB
JavaScript

import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import http from "node:http";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } 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-05A/csp-report.json");
const manifestPath = path.join(root, "tests/golden/M13-05A/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"];
assert.equal(typeof policy, "string");
const directives = new Map(policy.split(";").map((directive) => {
const tokens = directive.trim().split(/\s+/u);
return [tokens.shift(), tokens];
}));
const sourceTokens = [...directives.values()].flat();
for (const forbidden of ["'unsafe-inline'", "'unsafe-eval'", "data:", "*", "blob:"]) {
assert.equal(sourceTokens.includes(forbidden), false, `CSP contains forbidden token ${forbidden}`);
}
for (const required of ["default-src 'self'", "script-src 'self'", "worker-src 'self'", "connect-src 'self'", "object-src 'none'", "base-uri 'none'", "frame-ancestors 'none'"]) assert.ok(policy.includes(required), `CSP is missing ${required}`);
const index = fs.readFileSync(path.join(root, "web/app/index.html"), "utf8");
assert.doesNotMatch(index, /<script\b[^>]*(?:\b(?:src\s*=\s*["']data:|type\s*=\s*["']text\/javascript["']))/iu);
assert.doesNotMatch(index, /\bon[a-z]+\s*=/iu);
const productionSources = [];
function walk(directory) {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const file = path.join(directory, entry.name);
if (entry.isDirectory()) walk(file);
else if (/\.(?:ts|tsx|js|mjs|html)$/.test(entry.name)) productionSources.push(file);
}
}
walk(path.join(root, "web/app/src"));
for (const file of productionSources) {
const source = fs.readFileSync(file, "utf8");
assert.doesNotMatch(source, /\beval\s*\(|\bnew\s+Function\s*\(/u, `dynamic code in ${file}`);
assert.doesNotMatch(source, /(?:worker|script|src)\s*[:=]\s*["'`]data:/iu, `data script/worker in ${file}`);
}
const distRoot = path.join(root, "web/dist");
let buildChecked = false;
if (fs.existsSync(distRoot)) {
const builtFiles = [];
const walkBuilt = (directory) => {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const file = path.join(directory, entry.name);
if (entry.isDirectory()) walkBuilt(file);
else if (/\.(?:js|html)$/.test(entry.name)) builtFiles.push(file);
}
};
walkBuilt(distRoot);
for (const file of builtFiles) {
const source = fs.readFileSync(file, "utf8");
assert.doesNotMatch(source, /\beval\s*\(|\bnew\s+Function\s*\(/u, `dynamic code in built file ${file}`);
assert.doesNotMatch(source, /<script[^>]+\bsrc\s*=\s*["']data:/iu, `data script in built file ${file}`);
}
buildChecked = true;
}
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "m13-05a-csp-"));
fs.writeFileSync(path.join(fixtureRoot, "index.html"), index);
const server = createDeploymentHttpServer({ root: fixtureRoot, contract });
const origin = await listenDeploymentHttpServer(server);
try {
for (const pathname of ["/", "/missing", "/index.html"]) {
const response = await fetch(`${origin}${pathname}`);
assert.equal(response.headers.get("content-security-policy"), policy);
}
const report = { schemaVersion: 1, task: "M13-05A", operation: "CSP_POLICY", policy, sourceChecks: { inlineScript: "DENY", inlineHandler: "DENY", eval: "DENY", dataScript: "DENY", undeclaredConnect: "DENY" }, responseCount: 3, buildChecked, execution: "DISABLED", nextTask: "M13-05B" };
if (process.env.UPDATE_M13_05A_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-05A", parentTask: "M13-04J", nextTask: "M13-05B" });
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-policy-ok inline=DENY eval=DENY dataScript=DENY undeclaredConnect=DENY responses=3 execution=DISABLED next=M13-05B\n");
} finally {
await new Promise((resolve) => server.close(resolve));
fs.rmSync(fixtureRoot, { recursive: true, force: true });
}