fix(browser): decode CDP URL credentials

This commit is contained in:
VectorPeak
2026-07-01 19:14:20 +08:00
committed by GitHub
parent 06b841fa58
commit 1cf6ff3bdc
3 changed files with 40 additions and 1 deletions
@@ -199,6 +199,13 @@ describe("cdp.helpers", () => {
expect(headers.Authorization).toBe(`Basic ${Buffer.from("user:pass").toString("base64")}`);
});
it("decodes percent-encoded basic auth credentials from URLs", () => {
const headers = getHeadersWithAuth("https://alice:p%40ss%20word@example.com");
expect(headers.Authorization).toBe(
`Basic ${Buffer.from("alice:p@ss word").toString("base64")}`,
);
});
it("keeps preexisting authorization headers", () => {
const headers = getHeadersWithAuth("https://user:pass@example.com", {
Authorization: "Bearer token",
@@ -146,6 +146,28 @@ describe("cdp helpers", () => {
expect(release).toHaveBeenCalledTimes(1);
});
it("decodes URL credentials before sending guarded CDP auth headers", async () => {
const release = vi.fn(async () => {});
fetchWithSsrFGuardMock.mockResolvedValueOnce({
response: {
ok: true,
status: 200,
},
release,
});
await expect(
fetchOk("http://alice:p%40ss%20word@127.0.0.1:9222/json/version", 250),
).resolves.toBeUndefined();
const request = requireGuardedFetchRequest();
expect(request?.url).toBe("http://127.0.0.1:9222/json/version");
expect(request?.init?.headers).toEqual({
Authorization: `Basic ${Buffer.from("alice:p@ss word").toString("base64")}`,
});
expect(release).toHaveBeenCalledTimes(1);
});
it("preserves hostname allowlist while allowing exact loopback CDP fetches", async () => {
const release = vi.fn(async () => {});
fetchWithSsrFGuardMock.mockResolvedValueOnce({
+11 -1
View File
@@ -113,6 +113,14 @@ export type CdpSendFn = (
sessionId?: string,
) => Promise<unknown>;
function decodeUrlUserInfo(value: string): string {
try {
return decodeURIComponent(value);
} catch {
return value;
}
}
function rawCdpMessageToString(data: WebSocket.RawData): string {
if (typeof data === "string") {
return data;
@@ -141,7 +149,9 @@ export function getHeadersWithAuth(url: string, headers: Record<string, string>
return mergedHeaders;
}
if (parsed.username || parsed.password) {
const auth = Buffer.from(`${parsed.username}:${parsed.password}`).toString("base64");
const username = decodeUrlUserInfo(parsed.username);
const password = decodeUrlUserInfo(parsed.password);
const auth = Buffer.from(`${username}:${password}`).toString("base64");
return { ...mergedHeaders, Authorization: `Basic ${auth}` };
}
} catch {