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, /]*(?:\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, /]+\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 }); }