{"record":{"id":"dd37a9de5d0ecb73","repo":"BerriAI/litellm","slug":"only-proxy-admins-can-set-allowed-passthrough-rou","errorCode":null,"errorMessage":"Only proxy admins can set `allowed_passthrough_routes` on a {entity}.","messagePattern":"Only proxy admins can set `allowed_passthrough_routes` on a (.+?)\\.","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"litellm/proxy/management_endpoints/common_utils.py","lineNumber":123,"sourceCode":"    return user_api_key_dict.user_id\n\n\ndef _check_passthrough_routes_caller_permission(\n    data: BaseModel,\n    user_api_key_dict: UserAPIKeyAuth,\n    *,\n    entity: str = \"key\",\n) -> None:\n    \"\"\"\n    Only proxy admins may set `allowed_passthrough_routes` (top-level or under\n    `metadata`) — it short-circuits the role-based route gate, so keys and teams\n    must be gated identically.\n    \"\"\"\n    # view-only admins excluded by design; blocked upstream from writes anyway\n    if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:\n        return\n    if getattr(data, \"allowed_passthrough_routes\", None):\n        raise HTTPException(\n            status_code=403,\n            detail={\"error\": f\"Only proxy admins can set `allowed_passthrough_routes` on a {entity}.\"},\n        )\n    metadata: Final = getattr(data, \"metadata\", None)\n    if isinstance(metadata, dict) and metadata.get(\"allowed_passthrough_routes\"):\n        raise HTTPException(\n            status_code=403,\n            detail={\"error\": f\"Only proxy admins can set `metadata.allowed_passthrough_routes` on a {entity}.\"},\n        )\n\n\ndef _is_user_team_admin(user_api_key_dict: UserAPIKeyAuth, team_obj: LiteLLM_TeamTable) -> bool:\n    for member in team_obj.members_with_roles:\n        if (member.user_id is not None and member.user_id == user_api_key_dict.user_id) and member.role == \"admin\":\n            return True\n\n    return False\n","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/common_utils.py#L105-L141","documentation":"_check_passthrough_routes_caller_permission runs on key and team create/update paths: only role PROXY_ADMIN may set allowed_passthrough_routes, either as a top-level field or under metadata (both variants raise, with slightly different messages). The field is privileged because it short-circuits the role-based route gate — a non-admin key or team granting itself passthrough routes would escalate its own permissions, so keys and teams are gated identically. PROXY_ADMIN_VIEW_ONLY is intentionally excluded (and is blocked from writes upstream anyway).","triggerScenarios":"POST /key/generate, /key/update, /team/new, or /team/update issued by an internal_user or team admin that includes allowed_passthrough_routes at the top level or nested as metadata.allowed_passthrough_routes; self-service flows where users configure their own keys' passthrough list.","commonSituations":"Delegated team admins trying to open passthrough routes for their team's keys; automation copying an admin key's payload as a template for non-admin keys; frontends exposing the field to all users in the key editor.","solutions":["Remove allowed_passthrough_routes (both top-level and from metadata) from the payload when acting as a non-admin","Or have a proxy admin make the change (master key / proxy_admin-owned key), possibly defining routes globally in config instead of per-key","Audit payloads that template admin keys — strip privileged fields before reusing them for lower-privilege creates"],"exampleFix":"# before: team-admin key trying to open passthrough routes\ncurl -X POST http://localhost:4000/key/generate -H \"Authorization: Bearer sk-team-admin\" \\\n  -d '{\"team_id\": \"t1\", \"allowed_passthrough_routes\": [\"/v1/embeddings\"]}'\n# 403 Only proxy admins can set `allowed_passthrough_routes` on a key.\n\n# after: strip the field (non-admin) or use the master key (admin)\ncurl -X POST http://localhost:4000/key/generate -H \"Authorization: Bearer sk-team-admin\" -d '{\"team_id\": \"t1\"}'\ncurl -X POST http://localhost:4000/key/generate -H \"Authorization: Bearer $LITELLM_MASTER_KEY\" \\\n  -d '{\"team_id\": \"t1\", \"allowed_passthrough_routes\": [\"/v1/embeddings\"]}'","handlingStrategy":"validation","validationCode":"PRIVILEGED_FIELDS = {\"allowed_passthrough_routes\"}\n\ndef strip_privileged_fields(payload: dict, is_admin: bool) -> dict:\n    \"\"\"Remove fields a non-admin caller must not set (top-level and metadata).\"\"\"\n    if is_admin:\n        return payload\n    cleaned = {k: v for k, v in payload.items() if k not in PRIVILEGED_FIELDS}\n    md = cleaned.get(\"metadata\")\n    if isinstance(md, dict):\n        cleaned[\"metadata\"] = {k: v for k, v in md.items() if k not in PRIVILEGED_FIELDS}\n    return cleaned","typeGuard":"from typing import TypeGuard\n\nPRIVILEGED_FIELDS = frozenset({\"allowed_passthrough_routes\"})\n\ndef is_safe_non_admin_payload(payload: dict) -> TypeGuard[dict]:\n    \"\"\"True when no privileged passthrough fields appear at any level.\"\"\"\n    if any(k in PRIVILEGED_FIELDS for k in payload):\n        return False\n    md = payload.get(\"metadata\")\n    return not (isinstance(md, dict) and any(k in PRIVILEGED_FIELDS for k in md))","tryCatchPattern":"import httpx\n\ntry:\n    r = httpx.post(f\"{PROXY_URL}/key/generate\", json=payload, headers=hdrs)\n    r.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 403 and \"allowed_passthrough_routes\" in e.response.text:\n        # do not retry as-is: strip the field or escalate to an admin key\n        raise PermissionError(\n            \"passthrough routes are admin-only; remove the field or use an admin key\"\n        ) from e\n    raise","preventionTips":["Sanitize key/team payloads before reuse: strip allowed_passthrough_routes from templates","Define passthrough routes globally in the proxy config instead of per-key where possible","Keep a separate admin path for permission-changing operations; never expose the field in self-service UIs"],"tags":["litellm-proxy","authorization","privilege-escalation","keys","teams","security"],"backgroundTag":"insufficient-permissions","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}