BerriAI/litellm · error · HTTPException

Only proxy admins can set `allowed_passthrough_routes` on a

Error message

Only proxy admins can set `allowed_passthrough_routes` on a {entity}.

What it means

_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).

Source

Thrown at litellm/proxy/management_endpoints/common_utils.py:123

    return user_api_key_dict.user_id


def _check_passthrough_routes_caller_permission(
    data: BaseModel,
    user_api_key_dict: UserAPIKeyAuth,
    *,
    entity: str = "key",
) -> None:
    """
    Only proxy admins may set `allowed_passthrough_routes` (top-level or under
    `metadata`) — it short-circuits the role-based route gate, so keys and teams
    must be gated identically.
    """
    # view-only admins excluded by design; blocked upstream from writes anyway
    if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
        return
    if getattr(data, "allowed_passthrough_routes", None):
        raise HTTPException(
            status_code=403,
            detail={"error": f"Only proxy admins can set `allowed_passthrough_routes` on a {entity}."},
        )
    metadata: Final = getattr(data, "metadata", None)
    if isinstance(metadata, dict) and metadata.get("allowed_passthrough_routes"):
        raise HTTPException(
            status_code=403,
            detail={"error": f"Only proxy admins can set `metadata.allowed_passthrough_routes` on a {entity}."},
        )


def _is_user_team_admin(user_api_key_dict: UserAPIKeyAuth, team_obj: LiteLLM_TeamTable) -> bool:
    for member in team_obj.members_with_roles:
        if (member.user_id is not None and member.user_id == user_api_key_dict.user_id) and member.role == "admin":
            return True

    return False

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Remove allowed_passthrough_routes (both top-level and from metadata) from the payload when acting as a non-admin
  2. Or have a proxy admin make the change (master key / proxy_admin-owned key), possibly defining routes globally in config instead of per-key
  3. Audit payloads that template admin keys — strip privileged fields before reusing them for lower-privilege creates

Example fix

# before: team-admin key trying to open passthrough routes
curl -X POST http://localhost:4000/key/generate -H "Authorization: Bearer sk-team-admin" \
  -d '{"team_id": "t1", "allowed_passthrough_routes": ["/v1/embeddings"]}'
# 403 Only proxy admins can set `allowed_passthrough_routes` on a key.

# after: strip the field (non-admin) or use the master key (admin)
curl -X POST http://localhost:4000/key/generate -H "Authorization: Bearer sk-team-admin" -d '{"team_id": "t1"}'
curl -X POST http://localhost:4000/key/generate -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -d '{"team_id": "t1", "allowed_passthrough_routes": ["/v1/embeddings"]}'
Defensive patterns

Strategy: validation

Validate before calling

PRIVILEGED_FIELDS = {"allowed_passthrough_routes"}

def strip_privileged_fields(payload: dict, is_admin: bool) -> dict:
    """Remove fields a non-admin caller must not set (top-level and metadata)."""
    if is_admin:
        return payload
    cleaned = {k: v for k, v in payload.items() if k not in PRIVILEGED_FIELDS}
    md = cleaned.get("metadata")
    if isinstance(md, dict):
        cleaned["metadata"] = {k: v for k, v in md.items() if k not in PRIVILEGED_FIELDS}
    return cleaned

Type guard

from typing import TypeGuard

PRIVILEGED_FIELDS = frozenset({"allowed_passthrough_routes"})

def is_safe_non_admin_payload(payload: dict) -> TypeGuard[dict]:
    """True when no privileged passthrough fields appear at any level."""
    if any(k in PRIVILEGED_FIELDS for k in payload):
        return False
    md = payload.get("metadata")
    return not (isinstance(md, dict) and any(k in PRIVILEGED_FIELDS for k in md))

Try / catch

import httpx

try:
    r = httpx.post(f"{PROXY_URL}/key/generate", json=payload, headers=hdrs)
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 403 and "allowed_passthrough_routes" in e.response.text:
        # do not retry as-is: strip the field or escalate to an admin key
        raise PermissionError(
            "passthrough routes are admin-only; remove the field or use an admin key"
        ) from e
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/dd37a9de5d0ecb73. Report an issue: GitHub.