fix(security): XSS vulnerability in Canvas Host + Windows CI stability
- Replace innerHTML with safe DOM manipulation in canvas-host/server.ts - Add XSS prevention test case - Fix Windows CI "Worker exited unexpectedly" by using singleFork mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
57d9c09f6e
commit
ef32639752
@ -229,4 +229,37 @@ describe("canvas host", () => {
|
|||||||
await fs.rm(dir, { recursive: true, force: true });
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("default index.html uses safe DOM manipulation to prevent XSS", async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
|
||||||
|
|
||||||
|
const server = await startCanvasHost({
|
||||||
|
runtime: defaultRuntime,
|
||||||
|
rootDir: dir,
|
||||||
|
port: 0,
|
||||||
|
listenHost: "127.0.0.1",
|
||||||
|
allowInTests: true,
|
||||||
|
liveReload: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`http://127.0.0.1:${server.port}${CANVAS_HOST_PATH}/`);
|
||||||
|
const html = await res.text();
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
|
||||||
|
// Verify the HTML uses safe DOM manipulation methods
|
||||||
|
expect(html).toContain("document.createElement");
|
||||||
|
expect(html).toContain("textContent");
|
||||||
|
|
||||||
|
// Verify dangerous innerHTML assignment for status is not present
|
||||||
|
expect(html).not.toContain("statusEl.innerHTML");
|
||||||
|
|
||||||
|
// Verify the status element construction is present
|
||||||
|
expect(html).toContain("bridgeSpan.className");
|
||||||
|
expect(html).toContain("bridgeSpan.textContent");
|
||||||
|
} finally {
|
||||||
|
await server.close();
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -122,11 +122,17 @@ function defaultIndexHTML() {
|
|||||||
const hasHelper = () =>
|
const hasHelper = () =>
|
||||||
typeof window.moltbotSendUserAction === "function" ||
|
typeof window.moltbotSendUserAction === "function" ||
|
||||||
typeof window.clawdbotSendUserAction === "function";
|
typeof window.clawdbotSendUserAction === "function";
|
||||||
statusEl.innerHTML =
|
// Build status message safely using DOM manipulation to prevent XSS
|
||||||
"Bridge: " +
|
statusEl.textContent = "";
|
||||||
(hasHelper() ? "<span class='ok'>ready</span>" : "<span class='bad'>missing</span>") +
|
statusEl.appendChild(document.createTextNode("Bridge: "));
|
||||||
" · iOS=" + (hasIOS() ? "yes" : "no") +
|
|
||||||
" · Android=" + (hasAndroid() ? "yes" : "no");
|
const bridgeSpan = document.createElement("span");
|
||||||
|
bridgeSpan.className = hasHelper() ? "ok" : "bad";
|
||||||
|
bridgeSpan.textContent = hasHelper() ? "ready" : "missing";
|
||||||
|
statusEl.appendChild(bridgeSpan);
|
||||||
|
|
||||||
|
statusEl.appendChild(document.createTextNode(" · iOS=" + (hasIOS() ? "yes" : "no")));
|
||||||
|
statusEl.appendChild(document.createTextNode(" · Android=" + (hasAndroid() ? "yes" : "no")));
|
||||||
|
|
||||||
window.addEventListener("moltbot:a2ui-action-status", (ev) => {
|
window.addEventListener("moltbot:a2ui-action-status", (ev) => {
|
||||||
const d = ev && ev.detail || {};
|
const d = ev && ev.detail || {};
|
||||||
|
|||||||
@ -8,6 +8,9 @@ const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
|||||||
const isWindows = process.platform === "win32";
|
const isWindows = process.platform === "win32";
|
||||||
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
||||||
const ciWorkers = isWindows ? 2 : 3;
|
const ciWorkers = isWindows ? 2 : 3;
|
||||||
|
// Use single fork on Windows CI to prevent "Worker exited unexpectedly" errors
|
||||||
|
// caused by unstable child process handling in Windows GitHub Actions runners
|
||||||
|
const useSingleFork = isCI && isWindows;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -19,6 +22,12 @@ export default defineConfig({
|
|||||||
testTimeout: 120_000,
|
testTimeout: 120_000,
|
||||||
hookTimeout: isWindows ? 180_000 : 120_000,
|
hookTimeout: isWindows ? 180_000 : 120_000,
|
||||||
pool: "forks",
|
pool: "forks",
|
||||||
|
poolOptions: {
|
||||||
|
forks: {
|
||||||
|
// Single fork on Windows CI prevents "Worker exited unexpectedly" errors
|
||||||
|
singleFork: useSingleFork,
|
||||||
|
},
|
||||||
|
},
|
||||||
maxWorkers: isCI ? ciWorkers : localWorkers,
|
maxWorkers: isCI ? ciWorkers : localWorkers,
|
||||||
include: [
|
include: [
|
||||||
"src/**/*.test.ts",
|
"src/**/*.test.ts",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user