Skip to content

[Fix] Extend caller-permission checks to service-account + tighten raw-body acceptance#26493

Merged
yuneng-berri merged 2 commits intolitellm_yj_apr23from
litellm_key_route_caller_checks_addendum
Apr 25, 2026
Merged

[Fix] Extend caller-permission checks to service-account + tighten raw-body acceptance#26493
yuneng-berri merged 2 commits intolitellm_yj_apr23from
litellm_key_route_caller_checks_addendum

Conversation

@yuneng-berri
Copy link
Copy Markdown
Collaborator

Relevant issues

Follow-up to #26492 (already merged) addressing review notes on the same area.

Summary

Three follow-ups in key_management_endpoints.py:

  1. _check_allowed_routes_caller_permission now opts into the safe-preset
    carve-out only at the post-handle_key_type re-check inside
    _common_key_generation_helper. The raw-body call sites
    (/key/generate entry, _validate_update_key_data, regenerate_key_fn,
    and now /key/service-account/generate) keep the default strictness, so
    non-admins can no longer write allowed_routes=[\"llm_api_routes\"]
    directly in the request body and must go through key_type instead.

  2. /key/service-account/generate (generate_service_account_key_fn) now
    runs _check_allowed_routes_caller_permission and
    _check_passthrough_routes_caller_permission at entry, matching
    /key/generate. Previously a non-admin team admin could create a
    service-account key with metadata.allowed_passthrough_routes and
    reach admin-only routes through the passthrough gate.

  3. /key/regenerate mirrors /key/generate's post-handle_key_type
    re-check, so a key_type preset submitted via the regenerate body is
    routed through the same caller-permission helper before the request
    continues. Functionally a no-op today (the request fails downstream at
    the prisma layer because the column does not exist) but closes the
    surface against future schema or routing changes.

The legitimate key_type=llm_api and key_type=read_only flows for
non-admin callers continue to work, since the post-handle_key_type
call site is the only one that opts into the safe-preset carve-out.

Testing

uv run pytest \
  tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py \
  tests/test_litellm/proxy/auth/test_route_checks.py -x -q
# 315 passed

End-to-end against a live proxy with the master key and a fresh
internal-user key:

  • non-admin direct allowed_routes=[\"llm_api_routes\"] on /key/generate, /key/update, /key/regenerate → 403 (was 200 after the previous PR)
  • non-admin key_type=management on /key/regenerate → 403 (was 500 from a downstream column miss)
  • non-admin /key/service-account/generate with metadata.allowed_passthrough_routes → 403 (was 200)
  • non-admin key_type=llm_api and key_type=read_only on /key/generate → 200 (preset path still works)
  • admin equivalents and benign non-admin flows (key_alias only, /key/info on own key) → 200

Type

🐛 Bug Fix

Screenshots

@veria-ai
Copy link
Copy Markdown

veria-ai Bot commented Apr 25, 2026

Low: Security hardening of key management authorization

This PR tightens authorization checks in three key management code paths: it prevents non-admins from directly setting allowed_routes to safe preset values (they must use key_type instead), adds missing route/passthrough permission checks to the service account key generation endpoint, and mirrors the post-handle_key_type recheck in the key regeneration flow. All changes are security-positive and introduce no new attack surface.


Status: 0 open
Risk: 1/10

Posted by Veria AI · 2026-04-25T06:19:47.763Z

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 25, 2026

Greptile Summary

This PR tightens permission checks on three key-management endpoints by introducing an allow_safe_presets flag to _check_allowed_routes_caller_permission (default False), making raw-body call sites strictly reject non-admin writes to allowed_routes. The changes extend this strictness to the previously unguarded service-account endpoint and add a post-handle_key_type permission re-check to the regenerate endpoint that mirrors the existing check in _common_key_generation_helper. No P1 or P0 issues found; the three advertised security gaps are correctly closed and the safe preset path for non-admin callers using key_type remains functional.

Confidence Score: 5/5

Safe to merge — the three security gaps are correctly closed, existing non-admin preset flows continue to work, and the implementation is internally consistent across all call sites.

No P0 or P1 findings. All raw-body call sites consistently use allow_safe_presets=False; only the post-handle_key_type rechecks pass True. RegenerateKeyRequest inherits from GenerateKeyRequest so the handle_key_type(data, {}) call is type-safe. The intent is well-documented and the test evidence covers the patched surfaces.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/key_management_endpoints.py Adds allow_safe_presets flag to _check_allowed_routes_caller_permission, extends permission guards to /key/service-account/generate, and adds a post-handle_key_type re-check in regenerate_key_fn — all three security fixes appear correct and internally consistent

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Non-admin request body] --> B{allowed_routes set?}
    B -- Yes --> C[_check_allowed_routes\nallow_safe_presets=False]
    C --> D[403 Forbidden]
    B -- No --> E{key_type set?}
    E -- No --> F[Continue normally]
    E -- Yes --> G[handle_key_type derives routes]
    G --> H[_check_allowed_routes\nallow_safe_presets=True]
    H --> I{Routes in safe presets?}
    I -- Yes llm_api or read_only --> J[200 Key created]
    I -- No management_routes etc --> K[403 Forbidden]
    A --> L{passthrough routes set?}
    L -- Yes --> M[_check_passthrough_routes]
    M --> N[403 Forbidden]
    L -- No --> E
Loading

Reviews (2): Last reviewed commit: "docs: clarify intent of empty-dict handl..." | Re-trigger Greptile

Comment on lines +3975 to +3979
_check_allowed_routes_caller_permission(
allowed_routes=handle_key_type(data, {}).get("allowed_routes"),
user_api_key_dict=user_api_key_dict,
allow_safe_presets=True,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 handle_key_type invoked with empty dict as side-effect-free lookup

handle_key_type(data, {}) is called with a fresh empty dict purely to extract the key-type-derived allowed_routes. handle_key_type was designed to transform an existing data_json dict, so this usage is non-obvious and could silently break if someone later adds side effects on the passed dict or on data inside that function. Since the function's mapping logic is simple, consider an inline helper or at least a comment explaining why an empty dict is intentionally passed, to prevent a future contributor from "fixing" this into handle_key_type(data, data_json) which would have actual side effects here.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@yuneng-berri yuneng-berri force-pushed the litellm_key_route_caller_checks_addendum branch from bd914a6 to f7f77d0 Compare April 25, 2026 06:18
@yuneng-berri yuneng-berri merged commit 9426aff into litellm_yj_apr23 Apr 25, 2026
115 of 116 checks passed
@yuneng-berri yuneng-berri deleted the litellm_key_route_caller_checks_addendum branch April 25, 2026 06:41
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 25, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant