{"record":{"id":"3bd86563583e1d24","repo":"BerriAI/litellm","slug":"when-overriding-api-base-for-gemini-agents-you-mu","errorCode":null,"errorMessage":"When overriding api_base for Gemini agents, you must also supply an explicit api_key. Falling back to GOOGLE_API_KEY / GEMINI_API_KEY env vars with a custom api_base is refused to prevent leaking the shared provider key to arbitrary hosts.","messagePattern":"When overriding api_base for Gemini agents, you must also supply an explicit api_key\\. Falling back to GOOGLE_API_KEY / GEMINI_API_KEY env vars with a custom api_base is refused to prevent leaking the shared provider key to arbitrary hosts\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/gemini/agents/transformation.py","lineNumber":108,"sourceCode":"        litellm_params: dict[str, Any],\n    ) -> str:\n        return f\"{self._base_url(api_base)}/agents\"\n\n    def validate_environment(\n        self,\n        headers: dict[str, str],\n        litellm_params: dict[str, Any],\n    ) -> dict[str, str]:\n        headers = dict(headers)\n        headers[\"Content-Type\"] = \"application/json\"\n        explicit_api_key: Final = litellm_params.get(\"api_key\")\n        # SECURITY: when the caller overrides ``api_base``, refuse to fall back\n        # to the process-wide GOOGLE_API_KEY / GEMINI_API_KEY env vars. Otherwise\n        # an authenticated proxy user could set ``api_base`` to an attacker-\n        # controlled host and have the proxy ship its shared Gemini key in the\n        # ``x-goog-api-key`` header.\n        if litellm_params.get(\"api_base\") and not explicit_api_key:\n            raise ValueError(\n                \"When overriding api_base for Gemini agents, you must also \"\n                \"supply an explicit api_key. Falling back to GOOGLE_API_KEY / \"\n                \"GEMINI_API_KEY env vars with a custom api_base is refused \"\n                \"to prevent leaking the shared provider key to arbitrary hosts.\"\n            )\n        api_key: Final = GeminiModelInfo.get_api_key(explicit_api_key)\n        if not api_key:\n            raise ValueError(\"Google API key is required. Set GOOGLE_API_KEY or GEMINI_API_KEY, or pass api_key.\")\n        headers[\"x-goog-api-key\"] = api_key\n        return headers\n\n    def _raise_for_status(self, raw_response: httpx.Response) -> None:\n        if not (200 <= raw_response.status_code < 300):\n            raise GeminiError(\n                message=raw_response.text,\n                status_code=raw_response.status_code,\n                headers=dict(raw_response.headers),\n            )","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/gemini/agents/transformation.py#L90-L126","documentation":"A deliberate security guard in the Gemini agents transformation: when the caller overrides api_base (e.g. routing through a custom host or proxy), litellm refuses to attach the process-wide GOOGLE_API_KEY/GEMINI_API_KEY and instead raises this ValueError demanding an explicit api_key. Without the guard, an attacker who can set api_base could point the request at their own host and exfiltrate the shared Gemini key via the x-goog-api-key header.","triggerScenarios":"litellm_params contains api_base (custom proxy/gateway URL) but no api_key, e.g. completion(model='gemini/...', ..., api_base='https://my-proxy.example.com') while relying on env-var GEMINI_API_KEY.","commonSituations":"Routing Gemini agent traffic through an internal LLM gateway for logging/rate limiting; corporate proxy setups; testing against a local mock server. All previously worked by silently sending the env key to the overridden host, and now fail closed by design.","solutions":["Pass an explicit api_key together with the custom api_base: api_key='...' alongside api_base='...'.","If the proxy injects its own upstream credentials, pass a placeholder/dedicated key scoped to that hop.","Remove the api_base override to use the official Google endpoint, where the env-var fallback is allowed."],"exampleFix":"# before\nlitellm.completion(model=\"gemini/gemini-2.0-flash\", messages=m, api_base=\"https://llm-gw.internal\")  # ValueError\n\n# after\nlitellm.completion(model=\"gemini/gemini-2.0-flash\", messages=m, api_base=\"https://llm-gw.internal\", api_key=os.environ[\"GEMINI_GATEWAY_KEY\"])","handlingStrategy":"validation","validationCode":"def gemini_call_params(api_base: str | None, api_key: str | None) -> dict:\n    if api_base and not api_key:\n        raise ValueError(\n            \"Custom api_base for Gemini agents requires an explicit api_key \"\n            \"(env-var fallback is disabled for security)\"\n        )\n    return {k: v for k, v in {\"api_base\": api_base, \"api_key\": api_key}.items() if v}","typeGuard":"def is_safe_gemini_base_override(litellm_params: dict) -> bool:\n    return not litellm_params.get(\"api_base\") or bool(litellm_params.get(\"api_key\"))","tryCatchPattern":"try:\n    litellm.completion(model=\"gemini/gemini-2.0-flash\", messages=m, api_base=gw_url)\nexcept ValueError as e:\n    if \"must also supply an explicit api_key\" in str(e):\n        params[\"api_key\"] = gateway_key  # dedicated key for the overridden hop\n        litellm.completion(model=\"gemini/gemini-2.0-flash\", messages=m, **params)\n    else:\n        raise","preventionTips":["Treat api_base overrides as privileged configuration: always pair them with an explicit key in the same config object.","Give gateway/proxy hops their own dedicated key rather than reusing the production Gemini key.","Wrap agent-call construction in one factory that enforces the api_base-implies-api_key invariant."],"tags":["gemini","security","api-base","authentication","agents"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}