Skip to content

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

@lpcox

Description

@lpcox

Problem

When --enable-api-proxy is active and GEMINI_API_KEY is set, the Gemini CLI receives a placeholder key (gemini-api-key-placeholder-for-credential-isolation) inside the agent container. The api-proxy sidecar is supposed to intercept requests on port 10003, strip the placeholder, and inject the real key before forwarding to generativelanguage.googleapis.com.

After the fix in #1944 (v0.68.3), routing works correctly — the proxy reports Gemini=true and listens on port 10003. However, requests to Google still fail with API_KEY_INVALID:

_ApiError: {"error":{"code":400,"message":"API key not valid. Please pass a valid API key.",
"status":"INVALID_ARGUMENT","reason":"API_KEY_INVALID"}}

The real GEMINI_API_KEY is confirmed valid (direct calls to generativelanguage.googleapis.com succeed).

Root Cause Analysis

The Gemini CLI uses @google/genai SDK, which sends the API key via the x-goog-api-key HTTP header. The api-proxy (containers/api-proxy/server.js) handles this:

  1. Line 1019-1021: Injects {'x-goog-api-key': GEMINI_API_KEY} as injectHeaders
  2. Line 476-485 (proxyRequest): Copies incoming headers, then Object.assign(headers, injectHeaders) to overwrite with real key

The STRIPPED_HEADERS set (line 44-51) includes authorization and x-api-key but not x-goog-api-key. Since Node.js lowercases incoming headers, the Object.assign should overwrite the placeholder.

Possible causes to investigate

  1. The SDK sends the key as a query parameter (?key=...) rather than a header — The proxy does not strip or replace query-parameter keys, so the placeholder would be forwarded verbatim to Google. While the JS SDK uses the header approach, older SDK versions or the Gemini CLI itself may append key= to the URL.

  2. Header duplication — If for any reason the header appears multiple times (e.g., headers.append() vs headers.set()), Google might read the first occurrence (the placeholder) and ignore the real key injected later by Object.assign.

  3. Key encoding/truncation — The GEMINI_API_KEY env var might be truncated or encoding-mangled when passed through Docker environment variables to the api-proxy container.

Proposed Fix

  1. Add x-goog-api-key to STRIPPED_HEADERS — This ensures the placeholder is always removed before the real key is injected, eliminating any header duplication risk.

  2. Strip key= query parameter from the URL — In proxyRequest or in the Gemini-specific handler, detect and remove any key= query parameter from the forwarded URL to handle the query-parameter auth path:

    // In the Gemini handler or proxyRequest for Gemini
    const url = new URL(req.url, `http://${req.headers.host}`);
    url.searchParams.delete('key');
    req.url = url.pathname + url.search;
  3. Add debug logging — Log the injected key length/preview for Gemini requests (the other providers already have this at line 488-500 but only for x-api-key and authorization, not x-goog-api-key).

Reproduction

  1. Set GEMINI_API_KEY as a repository Actions secret
  2. Create a workflow with engine: gemini
  3. Trigger the workflow — observe API_KEY_INVALID error

Related

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions