mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 12:22:34 +03:00
Compare commits
1 Commits
fix/12517-
...
fix/12573-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
602df4d51d |
1
changelog.d/fixes/12573-gemini-cors-wildcard.md
Normal file
1
changelog.d/fixes/12573-gemini-cors-wildcard.md
Normal 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)
|
||||
@@ -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" },
|
||||
});
|
||||
}
|
||||
|
||||
70
tests/unit/gemini-cors-wildcard-bypass.test.ts
Normal file
70
tests/unit/gemini-cors-wildcard-bypass.test.ts
Normal 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"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user