Compare commits

...

1 Commits

Author SHA1 Message Date
diegosouzapw
602df4d51d fix(api): remove hardcoded wildcard CORS in openai-to-gemini-sse.ts (#12573)
open-sse/translator/response/openai-to-gemini-sse.ts hardcoded
Access-Control-Allow-Origin: "*" at six response sites (SSE success path
and five JSON response paths). The centralized fail-closed CORS gate
(applyCorsHeaders in src/server/cors/origins.ts) does run on this route
but only ever sets the header when it resolves an allowed origin — it
never clears a header a route handler already wrote. So the hardcoded
wildcard survived untouched for anonymous, credential-less requests on
the default REQUIRE_API_KEY=false install, letting any cross-origin page
read completion bodies via fetch().

Removes all six hardcoded literals so applyCorsHeaders is the sole
source of the header, matching every other route on this surface.
2026-09-10 14:00:33 -03:00
3 changed files with 76 additions and 6 deletions

View File

@@ -0,0 +1 @@
- fix(api): remove hardcoded wildcard CORS in openai-to-gemini-sse.ts so the centralized fail-closed CORS gate is the sole source of `Access-Control-Allow-Origin` (#12573)

View File

@@ -286,7 +286,6 @@ export function transformOpenAISSEToGeminiSSE(upstreamResponse: Response, model:
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Access-Control-Allow-Origin": "*",
},
});
}
@@ -348,7 +347,7 @@ export async function convertOpenAIResponseToGemini(
{ error: { message: sanitizeErrorMessage(err), code: response.status } },
{
status: response.status,
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
headers: { "Content-Type": "application/json" },
}
);
}
@@ -356,7 +355,7 @@ export async function convertOpenAIResponseToGemini(
// Already Gemini-shape (some upstreams may pre-translate) — pass through.
if (body.candidates) {
return Response.json(body, {
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
headers: { "Content-Type": "application/json" },
});
}
@@ -364,14 +363,14 @@ export async function convertOpenAIResponseToGemini(
if (body.error) {
return Response.json(body, {
status: response.status,
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
headers: { "Content-Type": "application/json" },
});
}
const choice = body.choices?.[0];
if (!choice || !choice.message) {
return Response.json(body, {
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
headers: { "Content-Type": "application/json" },
});
}
@@ -426,6 +425,6 @@ export async function convertOpenAIResponseToGemini(
}
return Response.json(geminiResponse, {
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
headers: { "Content-Type": "application/json" },
});
}

View File

@@ -0,0 +1,70 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { transformOpenAISSEToGeminiSSE } from "../../open-sse/translator/response/openai-to-gemini-sse";
import { applyCorsHeaders } from "../../src/server/cors/origins";
function buildUpstreamSSEResponse(): Response {
const encoder = new TextEncoder();
const sseBody = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(encoder.encode('data: {"choices":[{"delta":{"content":"hi"}}]}\n\n'));
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
controller.close();
},
});
return new Response(sseBody, {
status: 200,
headers: { "Content-Type": "text/event-stream" },
});
}
describe("issue #12573 — openai-to-gemini-sse must not pre-set Access-Control-Allow-Origin", () => {
it("does not hardcode a wildcard ACAO on the translated SSE response", () => {
const geminiResponse = transformOpenAISSEToGeminiSSE(buildUpstreamSSEResponse(), "gemini-test-model");
assert.equal(
geminiResponse.headers.get("Access-Control-Allow-Origin"),
null,
"the translator must not set its own ACAO — the centralized CORS gate is the sole source"
);
});
it("an anonymous request gets no Access-Control-Allow-Origin after the centralized gate runs (fail-closed)", () => {
const geminiResponse = transformOpenAISSEToGeminiSSE(buildUpstreamSSEResponse(), "gemini-test-model");
const anonymousRequest = new Request(
"http://localhost:20128/v1beta/models/gemini-test:streamGenerateContent",
{ method: "POST" }
);
applyCorsHeaders(geminiResponse, anonymousRequest, /* relaxForTokenAuth */ true);
assert.equal(
geminiResponse.headers.get("Access-Control-Allow-Origin"),
null,
"expected no Access-Control-Allow-Origin header for an anonymous request (fail-closed policy)"
);
});
it("a request carrying x-goog-api-key still gets the origin echoed by the centralized gate", () => {
const geminiResponse = transformOpenAISSEToGeminiSSE(buildUpstreamSSEResponse(), "gemini-test-model");
const authenticatedRequest = new Request(
"http://localhost:20128/v1beta/models/gemini-test:streamGenerateContent",
{
method: "POST",
headers: {
"x-goog-api-key": "test-key",
Origin: "http://localhost:3000",
},
}
);
applyCorsHeaders(geminiResponse, authenticatedRequest, /* relaxForTokenAuth */ true);
assert.equal(
geminiResponse.headers.get("Access-Control-Allow-Origin"),
"http://localhost:3000",
"expected the centralized gate to echo the request origin for a token-bearing request"
);
});
});