From 02297d4247697912a0b3194f589cbf82beae22ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:36:22 +0000 Subject: [PATCH 1/3] Initial plan From 314ecfe963b5c2506cd89894fb59626b0f9b9442 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:41:14 +0000 Subject: [PATCH 2/3] fix: strip apiKey/api_key Gemini params and add startup logging Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/0cf58c7b-8967-48bd-8a30-c02adabfd656 --- containers/api-proxy/server.js | 18 +++++++++++------- containers/api-proxy/server.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js index 829b632d..00156e2b 100644 --- a/containers/api-proxy/server.js +++ b/containers/api-proxy/server.js @@ -190,16 +190,16 @@ function buildUpstreamPath(reqUrl, targetHost, basePath) { } /** - * Strip the `key` query parameter from a Gemini request URL. + * Strip all known Gemini API-key query parameters from a request URL. * - * The @google/genai SDK (and older Gemini SDK versions) may append `?key=` - * to every request URL in addition to setting the `x-goog-api-key` header. - * The proxy injects the real key via the header, so the placeholder `key=` - * value must be removed before forwarding to Google to prevent - * API_KEY_INVALID errors. + * The @google/genai SDK (and older Gemini SDK versions) may append auth params + * (`?key=`, `?apiKey=`, or `?api_key=`) to every request URL in addition to + * setting the `x-goog-api-key` header. The proxy injects the real key via the + * header, so any placeholder param must be removed before forwarding to Google + * to prevent API_KEY_INVALID errors. * * @param {string} reqUrl - The incoming request URL (must start with exactly one '/') - * @returns {string} URL with the `key` query parameter removed + * @returns {string} URL with all Gemini auth query parameters removed */ function stripGeminiKeyParam(reqUrl) { // Only operate on relative request paths that begin with exactly one slash. @@ -211,6 +211,8 @@ function stripGeminiKeyParam(reqUrl) { } const parsed = new URL(reqUrl, 'http://localhost'); parsed.searchParams.delete('key'); + parsed.searchParams.delete('apiKey'); + parsed.searchParams.delete('api_key'); // Reconstruct relative path only — never emit the scheme/host from the dummy base. return parsed.pathname + parsed.search; } @@ -1085,6 +1087,7 @@ if (require.main === module) { }, 'gemini', GEMINI_API_BASE_PATH); }); + logRequest('info', 'server_start', { message: `GEMINI_API_KEY configured (length=${GEMINI_API_KEY.length})` }); geminiServer.listen(10003, '0.0.0.0', () => { logRequest('info', 'server_start', { message: 'Google Gemini proxy listening on port 10003', target: GEMINI_API_TARGET }); }); @@ -1107,6 +1110,7 @@ if (require.main === module) { socket.destroy(); }); + logRequest('warn', 'server_start', { message: 'GEMINI_API_KEY not set — Gemini proxy will return 503' }); geminiServer.listen(10003, '0.0.0.0', () => { logRequest('info', 'server_start', { message: 'Gemini endpoint listening on port 10003 (Gemini not configured — returning 503)' }); }); diff --git a/containers/api-proxy/server.test.js b/containers/api-proxy/server.test.js index 60b08865..01e5e62d 100644 --- a/containers/api-proxy/server.test.js +++ b/containers/api-proxy/server.test.js @@ -539,6 +539,31 @@ describe('stripGeminiKeyParam', () => { const result = stripGeminiKeyParam('/v1/generateContent?key=abc'); expect(result).toBe('/v1/generateContent'); }); + + it('should remove the apiKey= query parameter', () => { + expect(stripGeminiKeyParam('/v1/models/gemini-pro:generateContent?apiKey=placeholder')) + .toBe('/v1/models/gemini-pro:generateContent'); + }); + + it('should remove the api_key= query parameter', () => { + expect(stripGeminiKeyParam('/v1/models/gemini-pro:generateContent?api_key=placeholder')) + .toBe('/v1/models/gemini-pro:generateContent'); + }); + + it('should remove apiKey= while preserving other query parameters', () => { + expect(stripGeminiKeyParam('/v1/models/gemini-pro:generateContent?apiKey=placeholder&alt=json')) + .toBe('/v1/models/gemini-pro:generateContent?alt=json'); + }); + + it('should remove api_key= while preserving other query parameters', () => { + expect(stripGeminiKeyParam('/v1/models/gemini-pro:generateContent?api_key=placeholder&alt=json')) + .toBe('/v1/models/gemini-pro:generateContent?alt=json'); + }); + + it('should remove all auth params when multiple variants are present', () => { + expect(stripGeminiKeyParam('/v1/models/gemini-pro:generateContent?key=foo&apiKey=bar&api_key=baz&alt=json')) + .toBe('/v1/models/gemini-pro:generateContent?alt=json'); + }); }); // ── Helpers for proxyWebSocket tests ────────────────────────────────────────── From 06dfc8356d494fc0d742efbf8754bf28aa39cd16 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 04:36:24 +0000 Subject: [PATCH 3/3] fix: update stale comments and add missing 6th test case Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/46aa116c-47a9-4f66-8e20-13cb6f683aab Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> --- containers/api-proxy/server.js | 4 ++-- containers/api-proxy/server.test.js | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/containers/api-proxy/server.js b/containers/api-proxy/server.js index 00156e2b..f698e034 100644 --- a/containers/api-proxy/server.js +++ b/containers/api-proxy/server.js @@ -1070,7 +1070,7 @@ if (require.main === module) { const contentLength = parseInt(req.headers['content-length'], 10) || 0; if (checkRateLimit(req, res, 'gemini', contentLength)) return; - // Strip any ?key= query parameter — the @google/genai SDK may append it to the URL. + // Strip any auth query params (?key=, ?apiKey=, ?api_key=) — the SDK may append them. // The proxy injects the real key via x-goog-api-key header instead. req.url = stripGeminiKeyParam(req.url); @@ -1080,7 +1080,7 @@ if (require.main === module) { }); geminiServer.on('upgrade', (req, socket, head) => { - // Strip any ?key= query parameter — the @google/genai SDK may append it to the URL. + // Strip any auth query params (?key=, ?apiKey=, ?api_key=) — the SDK may append them. req.url = stripGeminiKeyParam(req.url); proxyWebSocket(req, socket, head, GEMINI_API_TARGET, { 'x-goog-api-key': GEMINI_API_KEY, diff --git a/containers/api-proxy/server.test.js b/containers/api-proxy/server.test.js index 01e5e62d..d0f0fd5a 100644 --- a/containers/api-proxy/server.test.js +++ b/containers/api-proxy/server.test.js @@ -564,6 +564,11 @@ describe('stripGeminiKeyParam', () => { expect(stripGeminiKeyParam('/v1/models/gemini-pro:generateContent?key=foo&apiKey=bar&api_key=baz&alt=json')) .toBe('/v1/models/gemini-pro:generateContent?alt=json'); }); + + it('should handle path with only api_key= param, leaving no trailing ?', () => { + const result = stripGeminiKeyParam('/v1/generateContent?api_key=abc'); + expect(result).toBe('/v1/generateContent'); + }); }); // ── Helpers for proxyWebSocket tests ──────────────────────────────────────────