fix(browser): land PR #11880 decodeURIComponent guardrails

Guard malformed percent-encoding in relay target routes and browser dispatcher params, add regression tests, and update changelog.
Landed from contributor @Yida-Dev (PR #11880).

Co-authored-by: Yida-Dev <reyifeijun@gmail.com>
This commit is contained in:
Peter Steinberger
2026-02-26 14:37:33 +00:00
parent 62a248eb99
commit 79659b2b14
5 changed files with 53 additions and 2 deletions

View File

@@ -208,6 +208,18 @@ describe("chrome extension relay server", () => {
expect(err.message).toContain("401");
});
it("returns 400 for malformed percent-encoding in target action routes", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;
await ensureChromeExtensionRelayServer({ cdpUrl });
const res = await fetch(`${cdpUrl}/json/activate/%E0%A4%A`, {
headers: relayAuthHeaders(cdpUrl),
});
expect(res.status).toBe(400);
expect(await res.text()).toContain("invalid targetId encoding");
});
it("deduplicates concurrent relay starts for the same requested port", async () => {
const port = await getFreePort();
cdpUrl = `http://127.0.0.1:${port}`;

View File

@@ -476,7 +476,14 @@ export async function ensureChromeExtensionRelayServer(opts: {
if (!match || (req.method !== "GET" && req.method !== "PUT")) {
return false;
}
const targetId = decodeURIComponent(match[1] ?? "").trim();
let targetId = "";
try {
targetId = decodeURIComponent(match[1] ?? "").trim();
} catch {
res.writeHead(400);
res.end("invalid targetId encoding");
return true;
}
if (!targetId) {
res.writeHead(400);
res.end("targetId required");

View File

@@ -23,6 +23,15 @@ vi.mock("./index.js", () => {
res.json({ ok: true });
},
);
app.get(
"/echo/:id",
async (
req: { params?: Record<string, string> },
res: { json: (body: unknown) => void },
) => {
res.json({ id: req.params?.id ?? null });
},
);
},
};
});
@@ -46,4 +55,19 @@ describe("browser route dispatcher (abort)", () => {
body: { error: expect.stringContaining("timed out") },
});
});
it("returns 400 for malformed percent-encoding in route params", async () => {
const { createBrowserRouteDispatcher } = await import("./dispatcher.js");
const dispatcher = createBrowserRouteDispatcher({} as BrowserRouteContext);
await expect(
dispatcher.dispatch({
method: "GET",
path: "/echo/%E0%A4%A",
}),
).resolves.toMatchObject({
status: 400,
body: { error: expect.stringContaining("invalid path parameter encoding") },
});
});
});

View File

@@ -87,7 +87,14 @@ export function createBrowserRouteDispatcher(ctx: BrowserRouteContext) {
for (const [idx, name] of match.paramNames.entries()) {
const value = exec[idx + 1];
if (typeof value === "string") {
params[name] = decodeURIComponent(value);
try {
params[name] = decodeURIComponent(value);
} catch {
return {
status: 400,
body: { error: `invalid path parameter encoding: ${name}` },
};
}
}
}
}