fix(ui): prevent Zod eval under strict Content Security Policy (#113617)

Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
This commit is contained in:
Peter Steinberger
2026-07-25 04:16:17 -07:00
committed by GitHub
parent 57e2f220de
commit d2ff17acc3
2 changed files with 74 additions and 48 deletions

View File

@@ -13,6 +13,10 @@
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<script>
// Configure Zod before module evaluation so its JIT probe cannot violate strict CSP.
globalThis.__zod_globalConfig ??= {};
globalThis.__zod_globalConfig.jitless = true;
(function () {
var THEMES = { claw: 1, knot: 1, dash: 1 };
var MODES = { system: 1, light: 1, dark: 1 };

View File

@@ -1,12 +1,12 @@
// Control UI tests cover startup under the Gateway's strict script policy.
import { chromium, type Browser } from "playwright";
import { chromium, firefox, type Browser, type BrowserContext } from "playwright";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import {
buildControlUiCspHeader,
computeInlineScriptHashes,
} from "../../../src/gateway/control-ui-csp.ts";
import {
canRunPlaywrightChromium,
canRunPlaywrightChromium as canRunPlaywrightBrowser,
installMockGateway,
resolvePlaywrightChromiumExecutablePath,
startControlUiE2eServer,
@@ -14,11 +14,25 @@ import {
} from "../test-helpers/control-ui-e2e.ts";
const chromiumExecutablePath = resolvePlaywrightChromiumExecutablePath(chromium.executablePath());
const chromiumAvailable = canRunPlaywrightChromium(chromiumExecutablePath);
const chromiumAvailable = canRunPlaywrightBrowser(chromiumExecutablePath);
const firefoxExecutablePath = firefox.executablePath();
const allowMissingChromium = process.env.OPENCLAW_UI_E2E_ALLOW_MISSING_CHROMIUM === "1";
const describeControlUiE2e = chromiumAvailable || !allowMissingChromium ? describe : describe.skip;
let browser: Browser;
const browsers: Array<{ name: string; launch: () => Promise<Browser> }> = [
{
name: "Chromium",
launch: () => chromium.launch({ executablePath: chromiumExecutablePath }),
},
];
if (canRunPlaywrightBrowser(firefoxExecutablePath)) {
browsers.push({
name: "Firefox",
launch: () => firefox.launch({ executablePath: firefoxExecutablePath }),
});
}
let server: ControlUiE2eServer;
describeControlUiE2e("Control UI strict CSP E2E", () => {
@@ -27,42 +41,43 @@ describeControlUiE2e("Control UI strict CSP E2E", () => {
throw new Error(`Playwright Chromium is unavailable at ${chromiumExecutablePath}`);
}
server = await startControlUiE2eServer();
browser = await chromium.launch({ executablePath: chromiumExecutablePath });
});
afterAll(async () => {
await browser?.close();
await server?.close();
});
it("starts without probing eval under the Gateway script policy", async () => {
const context = await browser.newContext({ serviceWorkers: "block" });
await context.addInitScript(() => {
const violations: Array<{ blockedUri: string; effectiveDirective: string }> = [];
Object.assign(globalThis, { __openclawCspViolations: violations });
document.addEventListener("securitypolicyviolation", (event) => {
violations.push({
blockedUri: event.blockedURI,
effectiveDirective: event.effectiveDirective,
});
});
});
const page = await context.newPage();
const gateway = await installMockGateway(page);
await page.route(server.baseUrl, async (route) => {
const response = await route.fetch();
const body = await response.text();
const csp = buildControlUiCspHeader({
inlineScriptHashes: computeInlineScriptHashes(body),
});
await route.fulfill({
body,
headers: { ...response.headers(), "content-security-policy": csp },
response,
});
});
it.each(browsers)("starts without probing eval in $name", async ({ launch }) => {
const browser = await launch();
let context: BrowserContext | undefined;
try {
context = await browser.newContext({ serviceWorkers: "block" });
await context.addInitScript(() => {
const violations: Array<{ blockedUri: string; effectiveDirective: string }> = [];
Object.assign(globalThis, { __openclawCspViolations: violations });
document.addEventListener("securitypolicyviolation", (event) => {
violations.push({
blockedUri: event.blockedURI,
effectiveDirective: event.effectiveDirective,
});
});
});
const page = await context.newPage();
const gateway = await installMockGateway(page);
await page.route(server.baseUrl, async (route) => {
const response = await route.fetch();
const body = await response.text();
const csp = buildControlUiCspHeader({
inlineScriptHashes: computeInlineScriptHashes(body),
});
await route.fulfill({
body,
headers: { ...response.headers(), "content-security-policy": csp },
response,
});
});
const response = await page.goto(server.baseUrl);
expect(response?.status()).toBe(200);
const csp = response?.headers()["content-security-policy"];
@@ -73,24 +88,31 @@ describeControlUiE2e("Control UI strict CSP E2E", () => {
.locator(".agent-chat__composer-combobox textarea")
.waitFor({ state: "visible", timeout: 10_000 });
const evalViolations = await page.evaluate(() => {
const violations = (
globalThis as typeof globalThis & {
__openclawCspViolations?: Array<{
blockedUri: string;
effectiveDirective: string;
}>;
}
)["__openclawCspViolations"];
return (violations ?? []).filter(
(violation) =>
violation.blockedUri === "eval" &&
violation.effectiveDirective.startsWith("script-src"),
);
const cspState = await page.evaluate(() => {
const runtime = globalThis as typeof globalThis & {
__openclawCspViolations?: Array<{
blockedUri: string;
effectiveDirective: string;
}>;
__zod_globalConfig?: { jitless?: boolean };
};
return {
jitless: runtime["__zod_globalConfig"]?.jitless === true,
evalViolations: (runtime["__openclawCspViolations"] ?? []).filter(
(violation) =>
violation.blockedUri === "eval" &&
violation.effectiveDirective.startsWith("script-src"),
),
};
});
expect(evalViolations).toEqual([]);
expect(cspState.jitless).toBe(true);
expect(cspState.evalViolations).toEqual([]);
} finally {
await context.close();
try {
await context?.close();
} finally {
await browser.close();
}
}
});
});