Security/Browser: constrain trace and download output paths to OpenClaw temp roots (#15652)

* Browser/Security: constrain trace and download output paths to temp roots

* Changelog: remove advisory ID from pre-public security note

* Browser/Security: constrain trace and download output paths to temp roots

* Changelog: remove advisory ID from pre-public security note

* test(bluebubbles): align timeout status expectation to 408

* test(discord): remove unused race-condition counter in threading test

* test(bluebubbles): align timeout status expectation to 408
This commit is contained in:
Mariano
2026-02-13 19:24:33 +00:00
committed by GitHub
parent 08725270e2
commit 7f0489e473
10 changed files with 166 additions and 16 deletions

View File

@@ -49,6 +49,7 @@ const pwMocks = vi.hoisted(() => ({
selectOptionViaPlaywright: vi.fn(async () => {}),
setInputFilesViaPlaywright: vi.fn(async () => {}),
snapshotAiViaPlaywright: vi.fn(async () => ({ snapshot: "ok" })),
traceStopViaPlaywright: vi.fn(async () => {}),
takeScreenshotViaPlaywright: vi.fn(async () => ({
buffer: Buffer.from("png"),
})),
@@ -434,14 +435,14 @@ describe("browser control server", () => {
expect(dialog).toMatchObject({ ok: true });
const waitDownload = await postJson(`${base}/wait/download`, {
path: "/tmp/report.pdf",
path: "report.pdf",
timeoutMs: 1111,
});
expect(waitDownload).toMatchObject({ ok: true });
const download = await postJson(`${base}/download`, {
ref: "e12",
path: "/tmp/report.pdf",
path: "report.pdf",
});
expect(download).toMatchObject({ ok: true });
@@ -480,4 +481,83 @@ describe("browser control server", () => {
expect(stopped.ok).toBe(true);
expect(stopped.stopped).toBe(true);
});
it("trace stop rejects traversal path outside trace dir", async () => {
const base = await startServerAndBase();
const res = await postJson<{ error?: string }>(`${base}/trace/stop`, {
path: "../../pwned.zip",
});
expect(res.error).toContain("Invalid path");
expect(pwMocks.traceStopViaPlaywright).not.toHaveBeenCalled();
});
it("trace stop accepts in-root relative output path", async () => {
const base = await startServerAndBase();
const res = await postJson<{ ok?: boolean; path?: string }>(`${base}/trace/stop`, {
path: "safe-trace.zip",
});
expect(res.ok).toBe(true);
expect(res.path).toContain("safe-trace.zip");
expect(pwMocks.traceStopViaPlaywright).toHaveBeenCalledWith(
expect.objectContaining({
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
path: expect.stringContaining("safe-trace.zip"),
}),
);
});
it("wait/download rejects traversal path outside downloads dir", async () => {
const base = await startServerAndBase();
const waitRes = await postJson<{ error?: string }>(`${base}/wait/download`, {
path: "../../pwned.pdf",
});
expect(waitRes.error).toContain("Invalid path");
expect(pwMocks.waitForDownloadViaPlaywright).not.toHaveBeenCalled();
});
it("download rejects traversal path outside downloads dir", async () => {
const base = await startServerAndBase();
const downloadRes = await postJson<{ error?: string }>(`${base}/download`, {
ref: "e12",
path: "../../pwned.pdf",
});
expect(downloadRes.error).toContain("Invalid path");
expect(pwMocks.downloadViaPlaywright).not.toHaveBeenCalled();
});
it("wait/download accepts in-root relative output path", async () => {
const base = await startServerAndBase();
const res = await postJson<{ ok?: boolean; download?: { path?: string } }>(
`${base}/wait/download`,
{
path: "safe-wait.pdf",
},
);
expect(res.ok).toBe(true);
expect(pwMocks.waitForDownloadViaPlaywright).toHaveBeenCalledWith(
expect.objectContaining({
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
path: expect.stringContaining("safe-wait.pdf"),
}),
);
});
it("download accepts in-root relative output path", async () => {
const base = await startServerAndBase();
const res = await postJson<{ ok?: boolean; download?: { path?: string } }>(`${base}/download`, {
ref: "e12",
path: "safe-download.pdf",
});
expect(res.ok).toBe(true);
expect(pwMocks.downloadViaPlaywright).toHaveBeenCalledWith(
expect.objectContaining({
cdpUrl: cdpBaseUrl,
targetId: "abcd1234",
ref: "e12",
path: expect.stringContaining("safe-download.pdf"),
}),
);
});
});