From 8985f23de740a18dd2f101cd75b6d6f3ba67d552 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 16 Feb 2026 03:35:21 +0100 Subject: [PATCH] test(gateway): move Control UI http coverage --- src/gateway/control-ui.http.test.ts | 106 ++++++++++++++++++++++++++++ src/gateway/gateway-misc.test.ts | 104 --------------------------- 2 files changed, 106 insertions(+), 104 deletions(-) create mode 100644 src/gateway/control-ui.http.test.ts diff --git a/src/gateway/control-ui.http.test.ts b/src/gateway/control-ui.http.test.ts new file mode 100644 index 0000000000..247f4595ee --- /dev/null +++ b/src/gateway/control-ui.http.test.ts @@ -0,0 +1,106 @@ +import type { IncomingMessage, ServerResponse } from "node:http"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { describe, expect, it, vi } from "vitest"; +import { CONTROL_UI_BOOTSTRAP_CONFIG_PATH } from "./control-ui-contract.js"; +import { handleControlUiHttpRequest } from "./control-ui.js"; + +const makeResponse = (): { + res: ServerResponse; + setHeader: ReturnType; + end: ReturnType; +} => { + const setHeader = vi.fn(); + const end = vi.fn(); + const res = { + headersSent: false, + statusCode: 200, + setHeader, + end, + } as unknown as ServerResponse; + return { res, setHeader, end }; +}; + +describe("handleControlUiHttpRequest", () => { + it("sets security headers for Control UI responses", async () => { + const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-")); + try { + await fs.writeFile(path.join(tmp, "index.html"), "\n"); + const { res, setHeader } = makeResponse(); + const handled = handleControlUiHttpRequest( + { url: "/", method: "GET" } as IncomingMessage, + res, + { + root: { kind: "resolved", path: tmp }, + }, + ); + expect(handled).toBe(true); + expect(setHeader).toHaveBeenCalledWith("X-Frame-Options", "DENY"); + const csp = setHeader.mock.calls.find((call) => call[0] === "Content-Security-Policy")?.[1]; + expect(typeof csp).toBe("string"); + expect(String(csp)).toContain("frame-ancestors 'none'"); + expect(String(csp)).toContain("script-src 'self'"); + expect(String(csp)).not.toContain("script-src 'self' 'unsafe-inline'"); + } finally { + await fs.rm(tmp, { recursive: true, force: true }); + } + }); + + it("does not inject inline scripts into index.html", async () => { + const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-ui-")); + try { + const html = "Hello\n"; + await fs.writeFile(path.join(tmp, "index.html"), html); + const { res, end } = makeResponse(); + const handled = handleControlUiHttpRequest( + { url: "/", method: "GET" } as IncomingMessage, + res, + { + root: { kind: "resolved", path: tmp }, + config: { + agents: { defaults: { workspace: tmp } }, + ui: { assistant: { name: ".png" } }, + }, + }, + ); + expect(handled).toBe(true); + const payload = String(end.mock.calls[0]?.[0] ?? ""); + const parsed = JSON.parse(payload) as { + basePath: string; + assistantName: string; + assistantAvatar: string; + assistantAgentId: string; + }; + expect(parsed.basePath).toBe(""); + expect(parsed.assistantName).toBe(".png" } }, - }, - }, - ); - expect(handled).toBe(true); - const payload = String(end.mock.calls[0]?.[0] ?? ""); - const parsed = JSON.parse(payload) as { - basePath: string; - assistantName: string; - assistantAvatar: string; - assistantAgentId: string; - }; - expect(parsed.basePath).toBe(""); - expect(parsed.assistantName).toBe("