diff --git a/packages/opencode/src/server/cors.ts b/packages/opencode/src/server/cors.ts index 92296a3b7dbf..905a22d1f62f 100644 --- a/packages/opencode/src/server/cors.ts +++ b/packages/opencode/src/server/cors.ts @@ -16,6 +16,7 @@ export function isAllowedCorsOrigin(input: string | undefined, opts?: CorsOption if (input === "tauri://localhost" || input === "http://tauri.localhost" || input === "https://tauri.localhost") return true if (opencodeOrigin.test(input)) return true + if (opts?.cors?.includes("*")) return true return opts?.cors?.includes(input) ?? false } diff --git a/packages/opencode/test/server/httpapi-cors.test.ts b/packages/opencode/test/server/httpapi-cors.test.ts index 4e9680c7ce4b..953b78366a95 100644 --- a/packages/opencode/test/server/httpapi-cors.test.ts +++ b/packages/opencode/test/server/httpapi-cors.test.ts @@ -119,4 +119,28 @@ describe("HttpApi CORS", () => { expect(rejected.headers.get("access-control-allow-origin")).not.toBe("https://evil.example") }), ) + + it.live("allows any origin when cors contains wildcard *", () => + Effect.gen(function* () { + const listener = yield* Effect.acquireRelease( + Effect.promise(() => Server.listen({ hostname: "127.0.0.1", port: 0, cors: ["*"] })), + (listener) => Effect.promise(() => listener.stop(true)), + ) + + const response = yield* Effect.promise(() => + fetch(new URL(InstancePaths.path, listener.url), { + method: "OPTIONS", + headers: { + origin: "https://any.origin.example", + "access-control-request-method": "GET", + "access-control-request-headers": "authorization", + }, + }), + ) + + expect(response.status).toBe(204) + expect(response.headers.get("access-control-allow-origin")).toBe("https://any.origin.example") + expect(response.headers.get("access-control-allow-headers")).toBe("authorization") + }), + ) })