mirror of https://github.com/openclaw/openclaw.git
fix(browser): validate cdp websocket pivots
This commit is contained in:
parent
e4ea3c03cf
commit
80720b4994
|
|
@ -227,6 +227,23 @@ describe("cdp", () => {
|
|||
expect(created.targetId).toBe("TARGET_LOCAL");
|
||||
});
|
||||
|
||||
it("blocks cross-host websocket pivots returned by /json/version in strict SSRF mode", async () => {
|
||||
const httpPort = await startVersionHttpServer({
|
||||
webSocketDebuggerUrl: "ws://169.254.169.254:9222/devtools/browser/PIVOT",
|
||||
});
|
||||
|
||||
await expect(
|
||||
createTargetViaCdp({
|
||||
cdpUrl: `http://127.0.0.1:${httpPort}`,
|
||||
url: "https://example.com",
|
||||
ssrfPolicy: {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
allowedHostnames: ["127.0.0.1"],
|
||||
},
|
||||
}),
|
||||
).rejects.toBeInstanceOf(SsrFBlockedError);
|
||||
});
|
||||
|
||||
it("evaluates javascript via CDP", async () => {
|
||||
const wsPort = await startWsServerWithMessages((msg, socket) => {
|
||||
if (msg.method === "Runtime.enable") {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import {
|
||||
appendCdpPath,
|
||||
assertCdpEndpointAllowed,
|
||||
fetchJson,
|
||||
isLoopbackHost,
|
||||
isWebSocketUrl,
|
||||
|
|
@ -194,6 +195,7 @@ export async function createTargetViaCdp(opts: {
|
|||
if (!wsUrl) {
|
||||
throw new Error("CDP /json/version missing webSocketDebuggerUrl");
|
||||
}
|
||||
await assertCdpEndpointAllowed(wsUrl, opts.ssrfPolicy);
|
||||
}
|
||||
|
||||
return await withCdpSocket(wsUrl, async (send) => {
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ import os from "node:os";
|
|||
import path from "node:path";
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { WebSocketServer } from "ws";
|
||||
import { SsrFBlockedError } from "../infra/net/ssrf.js";
|
||||
import {
|
||||
decorateOpenClawProfile,
|
||||
ensureProfileCleanExit,
|
||||
findChromeExecutableMac,
|
||||
findChromeExecutableWindows,
|
||||
getChromeWebSocketUrl,
|
||||
isChromeCdpReady,
|
||||
isChromeReachable,
|
||||
resolveBrowserExecutableForPlatform,
|
||||
|
|
@ -328,6 +330,39 @@ describe("browser chrome helpers", () => {
|
|||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("blocks cross-host websocket pivots returned by /json/version in strict SSRF mode", async () => {
|
||||
const server = createServer((req, res) => {
|
||||
if (req.url === "/json/version") {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
webSocketDebuggerUrl: "ws://169.254.169.254:9222/devtools/browser/pivot",
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.listen(0, "127.0.0.1", () => resolve());
|
||||
server.once("error", reject);
|
||||
});
|
||||
|
||||
try {
|
||||
const addr = server.address() as AddressInfo;
|
||||
await expect(
|
||||
getChromeWebSocketUrl(`http://127.0.0.1:${addr.port}`, 50, {
|
||||
dangerouslyAllowPrivateNetwork: false,
|
||||
allowedHostnames: ["127.0.0.1"],
|
||||
}),
|
||||
).rejects.toBeInstanceOf(SsrFBlockedError);
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
}
|
||||
});
|
||||
|
||||
it("reports cdpReady only when Browser.getVersion command succeeds", async () => {
|
||||
await withMockChromeCdpServer({
|
||||
wsPath: "/devtools/browser/health",
|
||||
|
|
|
|||
|
|
@ -200,7 +200,9 @@ export async function getChromeWebSocketUrl(
|
|||
if (!wsUrl) {
|
||||
return null;
|
||||
}
|
||||
return normalizeCdpWsUrl(wsUrl, cdpUrl);
|
||||
const normalizedWsUrl = normalizeCdpWsUrl(wsUrl, cdpUrl);
|
||||
await assertCdpEndpointAllowed(normalizedWsUrl, ssrfPolicy);
|
||||
return normalizedWsUrl;
|
||||
}
|
||||
|
||||
async function canRunCdpHealthCommand(
|
||||
|
|
|
|||
Loading…
Reference in New Issue