{"record":{"id":"962dd7ccfc26d938","repo":"BerriAI/litellm","slug":"gemini-managed-agent-endpoints-require-a-caller-su","errorCode":null,"errorMessage":"Gemini managed-agent endpoints require a caller-supplied Gemini api_key (via 'litellm_params_template'). Falling back to the proxy's GOOGLE_API_KEY / GEMINI_API_KEY env vars is only permitted for proxy admins.","messagePattern":"Gemini managed-agent endpoints require a caller-supplied Gemini api_key \\(via 'litellm_params_template'\\)\\. Falling back to the proxy's GOOGLE_API_KEY / GEMINI_API_KEY env vars is only permitted for proxy admins\\.","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"litellm/proxy/google_endpoints/agents_endpoints.py","lineNumber":62,"sourceCode":") -> None:\n    \"\"\"\n    SECURITY: refuse to use the proxy's shared GOOGLE_API_KEY / GEMINI_API_KEY\n    env fallback for non-admin callers on Gemini managed-agent CRUD endpoints.\n\n    These endpoints are part of ``llm_api_routes`` so any authenticated LLM key\n    can reach them, but unlike ``/v1beta/models/...:generateContent`` they are\n    *not* routed through ``model_list`` — the only credential source is either\n    the per-request ``litellm_params_template`` or the env var fallback. Without\n    this guard, any ordinary proxy user could list, create, or delete managed\n    agents inside the operator's Gemini project using the operator's key.\n\n    Proxy admins (master key) keep the env-fallback convenience for ops use.\n    \"\"\"\n    if _is_proxy_admin(user_api_key_dict):\n        return\n    if data.get(\"api_key\"):\n        return\n    raise HTTPException(\n        status_code=status.HTTP_401_UNAUTHORIZED,\n        detail=(\n            \"Gemini managed-agent endpoints require a caller-supplied \"\n            \"Gemini api_key (via 'litellm_params_template'). Falling back to \"\n            \"the proxy's GOOGLE_API_KEY / GEMINI_API_KEY env vars is only \"\n            \"permitted for proxy admins.\"\n        ),\n    )\n\n\ndef _merge_query_params_into_data(data: dict, request: Request) -> dict:\n    \"\"\"\n    For GET/DELETE endpoints that cannot carry a JSON body, read a\n    JSON-encoded ``litellm_params_template`` query parameter and merge its\n    contents into *data*, without overwriting keys that are already present\n    (e.g. path params like ``name`` or the fixed ``custom_llm_provider``).\n\n    This mirrors the ``litellm_params_template`` handling in","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/google_endpoints/agents_endpoints.py#L44-L80","documentation":"Security guard on the Gemini managed-agent CRUD routes (POST/GET/DELETE /v1beta/agents...). These routes are reachable by any authenticated proxy key but are NOT routed through model_list, so the only credential sources are the per-request litellm_params_template or the proxy's GOOGLE_API_KEY/GEMINI_API_KEY env fallback. To stop ordinary users from creating/deleting agents in the operator's Gemini project with the operator's key, non-admin callers who did not supply their own api_key get HTTP 401.","triggerScenarios":"A non-admin virtual key calling POST /v1beta/agents (or GET/DELETE variants) without an api_key inside the litellm_params_template body field, or for GET/DELETE without the JSON-encoded litellm_params_template query parameter containing api_key.","commonSituations":"Multi-tenant proxies where users were previously calling /v1beta/models/...:generateContent successfully via the shared env key and assume agent endpoints work the same way; passing ?api_key=... as a flat query param (unsupported and insecure) instead of the template; admin testing with a non-master key by mistake.","solutions":["Include the caller's own Gemini key: on POST send litellm_params_template: {\"api_key\": \"AIza...\"} in the JSON body; on GET/DELETE pass ?litellm_params_template=%7B%22api_key%22%3A%22AIza...%22%7D (URL-encoded JSON)","Or perform the operation with the master key / a PROXY_ADMIN role key, which may use the env fallback","If every user should use their own Google credentials, configure per-key/per-team litellm_params so api_key is injected into data before the guard runs","Never pass the key as a bare ?api_key= query parameter — it is both unsupported and leaks into access logs"],"exampleFix":"# before (non-admin key, no credential)\ncurl http://localhost:4000/v1beta/agents -H 'Authorization: Bearer sk-user-key'\n\n# after\ncurl -X POST http://localhost:4000/v1beta/agents \\\n  -H 'Authorization: Bearer sk-user-key' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"litellm_params_template\": {\"api_key\": \"AIza...\"}, ...agent payload...}'","handlingStrategy":"validation","validationCode":"def gemini_agent_headers_and_body(api_key: str, payload: dict) -> dict:\n    \"\"\"Non-admin calls must embed a Gemini key in litellm_params_template.\"\"\"\n    payload[\"litellm_params_template\"] = {\"api_key\": api_key}\n    return payload\n\n# GET/DELETE: encode the template in the query string instead\n# from urllib.parse import urlencode\n# qs = urlencode({\"litellm_params_template\": json.dumps({\"api_key\": api_key})})","typeGuard":"def agent_call_is_authorized(is_admin: bool, data: dict) -> bool:\n    \"\"\"Non-admin needs data['api_key'] set (merged from litellm_params_template).\"\"\"\n    return is_admin or bool(data.get(\"api_key\"))","tryCatchPattern":"try:\n    r = requests.post(f\"{proxy}/v1beta/agents\", json=body, headers=h, timeout=30)\n    r.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response is not None and e.response.status_code == 401 and \"litellm_params_template\" in e.response.text:\n        raise PermissionError(\"Supply the caller's Gemini api_key via litellm_params_template\") from e\n    raise","preventionTips":["Never rely on the proxy's GOOGLE_API_KEY env var for agent CRUD as a non-admin","Use the JSON-encoded litellm_params_template query param on GET/DELETE, never ?api_key=","Keep per-user Gemini keys in your secret store and inject them at call time","Reserve env-fallback usage for admin/ops scripts using the master key"],"tags":["litellm-proxy","gemini","agents","authentication","api-key","multi-tenant"],"backgroundTag":"missing-api-key","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-23T11:17:13.642Z"}