From 6fada51fe8a98dc6f288760da82e583cdcbfb1f7 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Tue, 7 Apr 2026 23:51:33 -0300 Subject: [PATCH] fix(security): resolve CodeQL scanning alerts for SSRF, insecure randomness and incomplete URLs --- open-sse/translator/helpers/geminiHelper.ts | 4 +++- scan_packet.json | 0 src/app/api/providers/[id]/sync-models/route.ts | 12 +++--------- tests/unit/chatcore-translation-paths.test.mjs | 6 +++--- .../unit/provider-validation-specialty.test.mjs | 16 ++++++++-------- 5 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 scan_packet.json diff --git a/open-sse/translator/helpers/geminiHelper.ts b/open-sse/translator/helpers/geminiHelper.ts index 01a9fc2392..7c20db724a 100644 --- a/open-sse/translator/helpers/geminiHelper.ts +++ b/open-sse/translator/helpers/geminiHelper.ts @@ -162,7 +162,9 @@ export function generateRequestId() { // Generate session ID export function generateSessionId() { - return `-${Math.floor(Math.random() * 9000000000000000000)}`; + const bytes = crypto.randomBytes(8); + const num = bytes.readBigUInt64LE() % 9000000000000000000n; + return `-${num.toString()}`; } // Helper: Remove unsupported keywords recursively from object/array diff --git a/scan_packet.json b/scan_packet.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/api/providers/[id]/sync-models/route.ts b/src/app/api/providers/[id]/sync-models/route.ts index 3bb2c5908e..95550c1d04 100644 --- a/src/app/api/providers/[id]/sync-models/route.ts +++ b/src/app/api/providers/[id]/sync-models/route.ts @@ -146,15 +146,9 @@ export async function POST(request: Request, { params }: { params: Promise<{ id: logProvider = toNonEmptyString(connection.provider) || "unknown"; channelLabel = getModelSyncChannelLabel(connection); - // Fetch models from the existing /api/providers/[id]/models endpoint - const parsedUrl = new URL(request.url); - if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") { - return NextResponse.json({ error: "Invalid protocol" }, { status: 400 }); - } - if (parsedUrl.hostname.includes("..")) { - return NextResponse.json({ error: "Invalid hostname" }, { status: 400 }); - } - const modelsUrl = `${parsedUrl.origin}/api/providers/${encodeURIComponent(id)}/models`; + // Fetch models from the existing /api/providers/[id]/models endpoint via localhost to prevent SSRF + const safeOrigin = `http://127.0.0.1:${process.env.PORT || 20128}`; + const modelsUrl = `${safeOrigin}/api/providers/${encodeURIComponent(id)}/models`; const modelsRes = await fetch(modelsUrl, { method: "GET", headers: { diff --git a/tests/unit/chatcore-translation-paths.test.mjs b/tests/unit/chatcore-translation-paths.test.mjs index da5f1c8315..5d1f849580 100644 --- a/tests/unit/chatcore-translation-paths.test.mjs +++ b/tests/unit/chatcore-translation-paths.test.mjs @@ -806,7 +806,7 @@ test("chatCore refreshes GitHub credentials after 401 and retries with the refre refreshedCredentials = updated; }, responseFactory(captured, seenCalls) { - if (captured.url.includes("api.github.com/copilot_internal/v2/token")) { + if (captured.url.includes("https://api.github.com/copilot_internal/v2/token")) { return new Response( JSON.stringify({ token: "copilot-refreshed-token", @@ -820,7 +820,7 @@ test("chatCore refreshes GitHub credentials after 401 and retries with the refre } const providerCalls = seenCalls.filter((entry) => - entry.url.includes("api.githubcopilot.com/") + entry.url.includes("https://api.githubcopilot.com/") ); if (providerCalls.length === 1) { return new Response( @@ -839,7 +839,7 @@ test("chatCore refreshes GitHub credentials after 401 and retries with the refre }); const payload = await result.response.json(); - const providerCalls = calls.filter((entry) => entry.url.includes("api.githubcopilot.com/")); + const providerCalls = calls.filter((entry) => entry.url.includes("https://api.githubcopilot.com/")); assert.equal(result.success, true); assert.equal(providerCalls.length, 2); diff --git a/tests/unit/provider-validation-specialty.test.mjs b/tests/unit/provider-validation-specialty.test.mjs index 3436da1c16..43679a7dbb 100644 --- a/tests/unit/provider-validation-specialty.test.mjs +++ b/tests/unit/provider-validation-specialty.test.mjs @@ -88,16 +88,16 @@ test("search provider validators cover success, client errors, server errors and globalThis.fetch = async (url, init = {}) => { calls.push({ url: String(url), init }); const target = String(url); - if (target.match(/search.brave.com/i)) { + if (target.match(/search\.brave\.com/i)) { return new Response(JSON.stringify({ results: [] }), { status: 200 }); } - if (target.match(/api.exa.ai/i)) { + if (target.match(/api\.exa\.ai/i)) { return new Response(JSON.stringify({ error: "bad key" }), { status: 403 }); } - if (target.match(/api.tavily.com/i)) { + if (target.match(/api\.tavily\.com/i)) { return new Response(JSON.stringify({ error: "server" }), { status: 503 }); } - if (target.match(/api.perplexity.ai/i)) { + if (target.match(/api\.perplexity\.ai/i)) { throw new Error("perplexity offline"); } throw new Error(`unexpected fetch: ${target}`); @@ -180,13 +180,13 @@ test("OpenAI-compatible validator covers /responses mode and final ping fallback test("Anthropic-compatible and Claude Code compatible validators cover direct success and bridge fallbacks", async () => { globalThis.fetch = async (url, init = {}) => { const target = String(url); - if (target.match(/anthropic-compatible.example.com/i) && init.method === "GET") { + if (target.match(/anthropic-compatible\.example\.com/i) && init.method === "GET") { return new Response(JSON.stringify({ data: [] }), { status: 200 }); } - if (target.match(/cc-compatible.example.com/i) && init.method === "GET") { + if (target.match(/cc-compatible\.example\.com/i) && init.method === "GET") { return new Response(JSON.stringify({ error: "bridge unavailable" }), { status: 500 }); } - if (target.match(/cc-compatible.example.com/i) && init.method === "POST") { + if (target.match(/cc-compatible\.example\.com/i) && init.method === "POST") { return new Response(JSON.stringify({ error: "rate limited" }), { status: 429 }); } throw new Error(`unexpected fetch: ${target}`); @@ -352,7 +352,7 @@ test("specialty validators cover remaining status branches for Deepgram, Assembl if (target.match(/inworld/i)) { throw new Error("inworld offline"); } - if (target.match(/dashscope.aliyuncs.com/i)) { + if (target.match(/dashscope\.aliyuncs\.com/i)) { return new Response(JSON.stringify({ error: "server" }), { status: 500 }); } if (target.match(/longcat/i)) {