mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 14:51:24 +00:00
fix(browser): land PR #23962 extension relay CORS fix
Reworks browser relay CORS handling for extension-origin preflight and JSON responses, adds regression tests, and updates changelog. Landed from contributor @miloudbelarebia (PR #23962). Co-authored-by: Miloud Belarebia <miloudbelarebia@users.noreply.github.com>
This commit is contained in:
@@ -208,6 +208,61 @@ describe("chrome extension relay server", () => {
|
||||
expect(err.message).toContain("401");
|
||||
});
|
||||
|
||||
it("allows CORS preflight from chrome-extension origins", async () => {
|
||||
const port = await getFreePort();
|
||||
cdpUrl = `http://127.0.0.1:${port}`;
|
||||
await ensureChromeExtensionRelayServer({ cdpUrl });
|
||||
|
||||
const origin = "chrome-extension://abcdefghijklmnop";
|
||||
const res = await fetch(`${cdpUrl}/json/version`, {
|
||||
method: "OPTIONS",
|
||||
headers: {
|
||||
Origin: origin,
|
||||
"Access-Control-Request-Method": "GET",
|
||||
"Access-Control-Request-Headers": "x-openclaw-relay-token",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(204);
|
||||
expect(res.headers.get("access-control-allow-origin")).toBe(origin);
|
||||
expect(res.headers.get("access-control-allow-headers") ?? "").toContain(
|
||||
"x-openclaw-relay-token",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects CORS preflight from non-extension origins", async () => {
|
||||
const port = await getFreePort();
|
||||
cdpUrl = `http://127.0.0.1:${port}`;
|
||||
await ensureChromeExtensionRelayServer({ cdpUrl });
|
||||
|
||||
const res = await fetch(`${cdpUrl}/json/version`, {
|
||||
method: "OPTIONS",
|
||||
headers: {
|
||||
Origin: "https://example.com",
|
||||
"Access-Control-Request-Method": "GET",
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
});
|
||||
|
||||
it("returns CORS headers on JSON responses for extension origins", async () => {
|
||||
const port = await getFreePort();
|
||||
cdpUrl = `http://127.0.0.1:${port}`;
|
||||
await ensureChromeExtensionRelayServer({ cdpUrl });
|
||||
|
||||
const origin = "chrome-extension://abcdefghijklmnop";
|
||||
const res = await fetch(`${cdpUrl}/json/version`, {
|
||||
headers: {
|
||||
Origin: origin,
|
||||
...relayAuthHeaders(cdpUrl),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("access-control-allow-origin")).toBe(origin);
|
||||
});
|
||||
|
||||
it("rejects extension websocket access without relay auth token", async () => {
|
||||
const port = await getFreePort();
|
||||
cdpUrl = `http://127.0.0.1:${port}`;
|
||||
|
||||
@@ -365,6 +365,38 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
||||
const server = createServer((req, res) => {
|
||||
const url = new URL(req.url ?? "/", info.baseUrl);
|
||||
const path = url.pathname;
|
||||
const origin = getHeader(req, "origin");
|
||||
const isChromeExtensionOrigin =
|
||||
typeof origin === "string" && origin.startsWith("chrome-extension://");
|
||||
|
||||
if (isChromeExtensionOrigin && origin) {
|
||||
// Let extension pages call relay HTTP endpoints cross-origin.
|
||||
res.setHeader("Access-Control-Allow-Origin", origin);
|
||||
res.setHeader("Vary", "Origin");
|
||||
}
|
||||
|
||||
// Handle CORS preflight requests from the browser extension.
|
||||
if (req.method === "OPTIONS") {
|
||||
if (origin && !isChromeExtensionOrigin) {
|
||||
res.writeHead(403);
|
||||
res.end("Forbidden");
|
||||
return;
|
||||
}
|
||||
const requestedHeaders = (getHeader(req, "access-control-request-headers") ?? "")
|
||||
.split(",")
|
||||
.map((header) => header.trim().toLowerCase())
|
||||
.filter((header) => header.length > 0);
|
||||
const allowedHeaders = new Set(["content-type", RELAY_AUTH_HEADER, ...requestedHeaders]);
|
||||
res.writeHead(204, {
|
||||
"Access-Control-Allow-Origin": origin ?? "*",
|
||||
"Access-Control-Allow-Methods": "GET, PUT, POST, OPTIONS",
|
||||
"Access-Control-Allow-Headers": Array.from(allowedHeaders).join(", "),
|
||||
"Access-Control-Max-Age": "86400",
|
||||
Vary: "Origin, Access-Control-Request-Headers",
|
||||
});
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.startsWith("/json")) {
|
||||
const token = getHeader(req, RELAY_AUTH_HEADER)?.trim();
|
||||
|
||||
Reference in New Issue
Block a user