feat(csp): support inline script hashes in Control UI CSP (#53307) thanks @BunsDev

Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Co-authored-by: Nova <nova@openknot.ai>
This commit is contained in:
Val Alexander
2026-03-23 21:35:33 -05:00
parent e530865274
commit a96eded4a0
5 changed files with 126 additions and 8 deletions

View File

@@ -1,3 +1,4 @@
import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import type { IncomingMessage } from "node:http";
import os from "node:os";
@@ -131,6 +132,27 @@ describe("handleControlUiHttpRequest", () => {
});
});
it("includes CSP hash for inline scripts in index.html", async () => {
const scriptContent = "(function(){ var x = 1; })();";
const html = `<html><head><script>${scriptContent}</script></head><body></body></html>\n`;
const expectedHash = createHash("sha256").update(scriptContent, "utf8").digest("base64");
await withControlUiRoot({
indexHtml: html,
fn: async (tmp) => {
const { res, setHeader } = makeMockHttpResponse();
handleControlUiHttpRequest({ url: "/", method: "GET" } as IncomingMessage, res, {
root: { kind: "resolved", path: tmp },
});
const cspCalls = setHeader.mock.calls.filter(
(call) => call[0] === "Content-Security-Policy",
);
const lastCsp = String(cspCalls[cspCalls.length - 1]?.[1] ?? "");
expect(lastCsp).toContain(`'sha256-${expectedHash}'`);
expect(lastCsp).not.toMatch(/script-src[^;]*'unsafe-inline'/);
},
});
});
it("does not inject inline scripts into index.html", async () => {
const html = "<html><head></head><body>Hello</body></html>\n";
await withControlUiRoot({