Skip to content

fix(api-proxy): fix Gemini API_KEY_INVALID with credential isolation#1995

Merged
lpcox merged 4 commits intomainfrom
copilot/fix-gemini-api-key-injection
Apr 15, 2026
Merged

fix(api-proxy): fix Gemini API_KEY_INVALID with credential isolation#1995
lpcox merged 4 commits intomainfrom
copilot/fix-gemini-api-key-injection

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 15, 2026

When --enable-api-proxy is active, the Gemini proxy on port 10003 was forwarding the placeholder x-goog-api-key header and/or a ?key= query parameter to Google verbatim, causing API_KEY_INVALID errors even though the real key was being injected.

Root causes

  1. x-goog-api-key not in STRIPPED_HEADERS — the placeholder sent by the Gemini CLI was being copied into the forwarded headers, then overwritten by Object.assign. If the SDK sent it as a separate header entry or Google read the first occurrence, the placeholder could survive injection.

  2. ?key= query parameter not stripped — the @google/genai SDK appends ?key=<value> to request URLs in addition to setting the header. The proxy had no mechanism to strip this, so the placeholder was forwarded as-is.

Changes

  • STRIPPED_HEADERS: adds x-goog-api-key so the placeholder is unconditionally removed before the real key is injected
  • stripGeminiKeyParam(): new helper that deletes the key search param from Gemini request URLs; applied in both the HTTP and WebSocket upgrade handlers:
    req.url = stripGeminiKeyParam(req.url); // remove ?key=<placeholder>
    proxyRequest(req, res, GEMINI_API_TARGET, { 'x-goog-api-key': GEMINI_API_KEY }, ...);
  • Debug logging: extends auth_inject log event to also capture x-goog-api-key injection (parity with x-api-key / authorization)
  • Exports + tests: exports shouldStripHeader and stripGeminiKeyParam and adds unit tests covering both, including the x-goog-api-key stripping behaviour

- Add x-goog-api-key to STRIPPED_HEADERS to ensure placeholder is
  always stripped before the real key is injected
- Add stripGeminiKeyParam() to remove ?key= query params from URLs
  (the @google/genai SDK may append key= in addition to the header)
- Apply stripGeminiKeyParam() in both HTTP and WebSocket Gemini handlers
- Extend auth_inject debug logging to cover x-goog-api-key
- Export shouldStripHeader and stripGeminiKeyParam for unit testing
- Add tests for shouldStripHeader and stripGeminiKeyParam

Agent-Logs-Url: https://github.com/github/gh-aw-firewall/sessions/e48bf273-4302-49fe-acde-42cbd46c679c
@lpcox lpcox marked this pull request as ready for review April 15, 2026 18:57
@lpcox lpcox requested a review from Mossaka as a code owner April 15, 2026 18:57
Copilot AI review requested due to automatic review settings April 15, 2026 18:57
Copilot AI changed the title [WIP] Fix Gemini API key injection to send valid key fix(api-proxy): fix Gemini API_KEY_INVALID with credential isolation Apr 15, 2026
Copilot AI requested a review from lpcox April 15, 2026 18:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the API proxy sidecar’s Gemini handling to avoid forwarding invalid/placeholder API keys by stripping Gemini-specific auth inputs before proxying requests upstream.

Changes:

  • Add x-goog-api-key to the proxy’s stripped header list and expose shouldStripHeader() for tests.
  • Strip the key= query parameter from Gemini HTTP and WebSocket upgrade request URLs via stripGeminiKeyParam().
  • Add unit tests covering x-goog-api-key header stripping and key= query parameter stripping.
Show a summary per file
File Description
containers/api-proxy/server.js Strips x-goog-api-key, adds stripGeminiKeyParam(), strips key= for Gemini HTTP/WS, and exports helpers for tests.
containers/api-proxy/server.test.js Adds unit tests for shouldStripHeader() and stripGeminiKeyParam().

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 2

Comment thread containers/api-proxy/server.js Outdated
Comment thread containers/api-proxy/server.js Outdated

// Log auth header injection for debugging credential-isolation issues
const injectedKey = injectHeaders['x-api-key'] || injectHeaders['authorization'];
const injectedKey = injectHeaders['x-api-key'] || injectHeaders['authorization'] || injectHeaders['x-goog-api-key'];
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 15, 2026

✅ Coverage Check Passed

Overall Coverage

Metric Base PR Delta
Lines 85.35% 85.43% 📈 +0.08%
Statements 85.24% 85.33% 📈 +0.09%
Functions 87.96% 87.96% ➡️ +0.00%
Branches 77.95% 78.00% 📈 +0.05%
📁 Per-file Coverage Changes (1 files)
File Lines (Before → After) Statements (Before → After)
src/docker-manager.ts 86.8% → 87.1% (+0.30%) 86.4% → 86.7% (+0.29%)

Coverage comparison generated by scripts/ci/compare-coverage.ts

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot mentioned this pull request Apr 15, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Address PR review feedback:
- Make injected-key detection case-insensitive so auth_inject
  debug logs fire for OpenAI/Copilot (which use capital-A
  'Authorization') in addition to Anthropic and Gemini.
- Clarify stripGeminiKeyParam guard comments explaining why
  absolute/protocol-relative URLs are rejected before parsing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results

  • GitHub MCP: "Activate smoke-opencode workflow via copilot engine workaround" / "fix: increase token optimizer timeout and tighten prompt"
  • Playwright: github.com title contains "GitHub"
  • File Write: /tmp/gh-aw/agent/smoke-test-claude-24477096714.txt created
  • Bash: File contents verified

Overall: PASS

💥 [THE END] — Illustrated by Smoke Claude

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test Results — OpenCode

Test Result
GitHub MCP: last 2 merged PRs reviewed
→ "Activate smoke-opencode workflow via copilot engine workaround"
→ "fix: increase token optimizer timeout and tighten prompt"
File writing (smoke-test-opencode-24477096778.txt)
Bash tool (file verified via cat)
Build AWF (npm ci && npm run build)

Overall: PASS

🌐 Transmitted by Smoke OpenCode

@github-actions
Copy link
Copy Markdown
Contributor

🔥 Smoke Test: Copilot Engine — PASS

Test Result
GitHub MCP (list_pull_requests#1991: "Activate smoke-opencode workflow...")
GitHub.com connectivity (HTTP 200)
File write/read (smoke-test-copilot-24477096752.txt)

PR: fix(api-proxy): fix Gemini API_KEY_INVALID with credential isolation
Author: @app/copilot-swe-agent · Assignees: @lpcox, @Copilot

📰 BREAKING: Report filed by Smoke Copilot

@github-actions
Copy link
Copy Markdown
Contributor

Smoke test results:

  1. ✅ GitHub MCP merged PRs: "Activate smoke-opencode workflow via copilot engine workaround"; "fix: increase token optimizer timeout and tighten prompt"
  2. ❌ safeinputs-gh CLI query (tool unavailable)
  3. ✅ Playwright title check (contains "GitHub")
  4. ❌ Tavily web search (tool unavailable)
  5. ✅ File write test
  6. ✅ Bash cat verification
  7. ❌ Discussion query/comment test (tool unavailable)
  8. ✅ Build AWF (npm ci && npm run build)
    Overall status: FAIL

🔮 The oracle has spoken through Smoke Codex

@github-actions
Copy link
Copy Markdown
Contributor

Chroot Version Comparison Results

Runtime Host Version Chroot Version Match?
Python Python 3.12.13 Python 3.12.3 ❌ NO
Node.js v24.14.1 v20.20.2 ❌ NO
Go go1.22.12 go1.22.12 ✅ YES

Overall: ❌ Not all tests passed — Python and Node.js versions differ between host and chroot.

Tested by Smoke Chroot

@github-actions
Copy link
Copy Markdown
Contributor

Smoke Test: GitHub Actions Services Connectivity ✅

Check Result
Redis PING (host.docker.internal:6379) PONG
PostgreSQL ready (host.docker.internal:5432) ✅ accepting connections
PostgreSQL SELECT 1 (smoketest db, user postgres) ✅ returned 1

All checks passed. (redis-cli not installed; Redis tested via nc with raw RESP protocol.)

🔌 Service connectivity validated by Smoke Services

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Build Test Suite Results

Ecosystem Project Build/Install Tests Status
Bun elysia 1/1 passed ✅ PASS
Bun hono 1/1 passed ✅ PASS
C++ fmt N/A ✅ PASS
C++ json N/A ✅ PASS
Deno oak N/A 1/1 passed ✅ PASS
Deno std N/A 1/1 passed ✅ PASS
.NET hello-world N/A ✅ PASS
.NET json-parse N/A ✅ PASS
Go color 1/1 passed ✅ PASS
Go env 1/1 passed ✅ PASS
Go uuid 1/1 passed ✅ PASS
Java gson 1/1 passed ✅ PASS
Java caffeine 1/1 passed ✅ PASS
Node.js clsx passed ✅ PASS
Node.js execa passed ✅ PASS
Node.js p-limit passed ✅ PASS
Rust fd 1/1 passed ✅ PASS
Rust zoxide 1/1 passed ✅ PASS

Overall: 8/8 ecosystems passed — ✅ PASS

Generated by Build Test Suite for issue #1995 · ● 922.6K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API proxy: Gemini key injection sends placeholder to Google (API_KEY_INVALID)

3 participants