fix(browser): preserve loopback hostname allowlists

This commit is contained in:
Mason Huang
2026-04-14 13:21:24 +08:00
parent be41c90bf5
commit 0c2cc42bd4
2 changed files with 48 additions and 6 deletions

View File

@@ -51,6 +51,15 @@ describe("cdp helpers", () => {
).resolves.toBeUndefined();
});
it("still enforces hostname allowlist for loopback CDP endpoints", async () => {
await expect(
assertCdpEndpointAllowed("http://127.0.0.1:9222/json/version", {
dangerouslyAllowPrivateNetwork: false,
hostnameAllowlist: ["*.corp.example"],
}),
).rejects.toThrow("browser endpoint blocked by policy");
});
it("releases guarded CDP fetches for bodyless requests", async () => {
const release = vi.fn(async () => {});
fetchWithSsrFGuardMock.mockResolvedValueOnce({
@@ -98,4 +107,34 @@ describe("cdp helpers", () => {
);
expect(release).toHaveBeenCalledTimes(1);
});
it("preserves hostname allowlist while allowing exact loopback CDP fetches", async () => {
const release = vi.fn(async () => {});
fetchWithSsrFGuardMock.mockResolvedValueOnce({
response: {
ok: true,
status: 200,
},
release,
});
await expect(
fetchOk("http://127.0.0.1:9222/json/version", 250, undefined, {
dangerouslyAllowPrivateNetwork: false,
hostnameAllowlist: ["*.corp.example"],
}),
).resolves.toBeUndefined();
expect(fetchWithSsrFGuardMock).toHaveBeenCalledWith(
expect.objectContaining({
url: "http://127.0.0.1:9222/json/version",
policy: {
dangerouslyAllowPrivateNetwork: false,
hostnameAllowlist: ["*.corp.example"],
allowedHostnames: ["127.0.0.1"],
},
}),
);
expect(release).toHaveBeenCalledTimes(1);
});
});

View File

@@ -68,14 +68,17 @@ export async function assertCdpEndpointAllowed(
if (!["http:", "https:", "ws:", "wss:"].includes(parsed.protocol)) {
throw new Error(`Invalid CDP URL protocol: ${parsed.protocol.replace(":", "")}`);
}
// Loopback CDP endpoints are internal browser-control hops, not
// agent-controlled navigation targets.
if (isLoopbackHost(parsed.hostname)) {
return;
}
try {
const policy = isLoopbackHost(parsed.hostname)
? {
...ssrfPolicy,
allowedHostnames: Array.from(
new Set([...(ssrfPolicy?.allowedHostnames ?? []), parsed.hostname]),
),
}
: ssrfPolicy;
await resolvePinnedHostnameWithPolicy(parsed.hostname, {
policy: ssrfPolicy,
policy,
});
} catch (error) {
throw new BrowserCdpEndpointBlockedError({ cause: error });