Skip to content

feat(proxy): support object_permission in default_key_generate_params - #31776

Merged
krrish-berri-2 merged 3 commits into
litellm_internal_stagingfrom
litellm_default_key_object_permission
Jul 1, 2026
Merged

feat(proxy): support object_permission in default_key_generate_params#31776
krrish-berri-2 merged 3 commits into
litellm_internal_stagingfrom
litellm_default_key_object_permission

Conversation

@krrish-berri-2

@krrish-berri-2 krrish-berri-2 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Live proxy against a real Postgres DB, config with litellm_settings.default_key_generate_params.object_permission.vector_stores: ["default-proof-vs"]:

Key generated (as proxy admin) with no object_permission in the request picks up the default:

$ curl -s -X POST http://localhost:4000/key/generate \
    -H "Authorization: Bearer sk-1234" -d '{}' | jq -r .key
sk-...

$ curl -s "http://localhost:4000/key/info?key=$KEY" -H "Authorization: Bearer sk-1234" \
    | jq '.info.object_permission | {vector_stores, agents}'
{
  "vector_stores": ["default-proof-vs"],
  "agents": []
}

Key generated with a partial object_permission (only agents set) keeps the caller's explicit field and still gets the defaulted field merged in, instead of the default replacing the whole object:

$ curl -s -X POST http://localhost:4000/key/generate \
    -H "Authorization: Bearer sk-1234" \
    -d '{"object_permission": {"agents": ["explicit-agent"]}}' | jq -r .key
sk-...

$ curl -s "http://localhost:4000/key/info?key=$KEY" -H "Authorization: Bearer sk-1234" \
    | jq '.info.object_permission | {vector_stores, agents}'
{
  "vector_stores": ["default-proof-vs"],
  "agents": ["explicit-agent"]
}

A non-admin (alice, internal_user) creating her own personal key with no object_permission in the request is not rejected (the default is only merged in after the caller-scope validation, so it's never mistaken for a caller-requested team-scoped permission):

$ curl -s -X POST http://localhost:4000/key/generate \
    -H "Authorization: Bearer $ALICE_KEY" -d '{}' -o /dev/null -w '%{http_code}\n'
200

$ curl -s "http://localhost:4000/key/info?key=$NEW_KEY" -H "Authorization: Bearer sk-1234" \
    | jq '.info.object_permission.vector_stores'
["default-proof-vs"]

Type

🆕 New Feature

Changes

default_key_generate_params (config.yaml litellm_settings.default_key_generate_params) filled a fixed whitelist of scalar fields plus a full-replace for models/metadata, but never touched object_permission at all, so there was no way for an admin to set a default object_permission value (e.g. vector_stores, and once #31486 lands, mcp_tool_search_enabled) applied to every new key.

_common_key_generation_helper in key_management_endpoints.py now merges the default object_permission into data_json field by field, after validate_key_mcp_servers_against_team / validate_key_search_tools_against_team / validate_key_vector_stores_against_team run. Doing the merge after those checks (rather than earlier on the request object) matters: those checks reject team-scoped fields like vector_stores on a personal key from a non-admin caller, and an admin-configured default merged in before the checks would look exactly like a caller-requested permission, turning ordinary non-admin personal key creation into a 403. If the caller didn't set object_permission, the default is applied wholesale; if the caller set a partial object_permission, only the fields they left unset are filled from the default, so an explicit field (e.g. mcp_servers) is never clobbered.

Caught by Greptile review (missing isinstance guard on a config-sourced value) and Codex review (the ordering issue described above) - both addressed in a follow-up commit with a regression test for the non-admin case.

default_key_generate_params filled in a fixed whitelist of scalar fields
plus a full-replace for models/metadata, but never touched object_permission,
so admins had no way to set a default (e.g. mcp_tool_search_enabled,
vector_stores) applied to every new key. Merge object_permission field-by-field
instead of replacing it wholesale, so a caller-supplied field (e.g. mcp_servers)
is preserved alongside defaulted fields the caller left unset.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends _common_key_generation_helper to merge object_permission from litellm.default_key_generate_params on a field-by-field basis, deliberately after all team-scope validation checks so that admin-configured defaults are never misidentified as caller-requested permissions.

  • The merge is inserted at the correct position in _common_key_generation_helper — after validate_key_mcp_servers_against_team, validate_key_search_tools_against_team, and validate_key_vector_stores_against_team — and uses setdefault so a caller's explicitly set field always wins over the default.
  • Four new mock-based unit tests cover absent, partial, non-overriding, and non-admin personal-key scenarios; asyncio_mode = "auto" in pyproject.toml means they run correctly without explicit @pytest.mark.asyncio decoration, matching the existing sibling test test_default_key_generate_params_duration.
  • An isinstance(_default_object_permission, dict) guard prevents a TypeError if the config value is not a plain dict, and a parallel isinstance(_caller_object_permission, dict) guard on the request-side value prevents silent failures when the caller-provided permission is not yet normalized to a dict.

Confidence Score: 5/5

Safe to merge — the change is a small, well-scoped addition that merges admin-configured defaults into data_json after all caller-scope validation, with no modifications to existing validation logic.

The insertion point is correct: default object_permission values are applied after team-scope checks, so they cannot trigger false-positive 403s for non-admin callers. Both the default value and the caller value are guarded with isinstance(..., dict) before any dict operations are performed. A shallow copy is used for the wholesale-apply case, leaving the shared config object unmodified. The four new tests cover the key behavioral contracts.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/management_endpoints/key_management_endpoints.py Adds object_permission merging from default_key_generate_params after team-scope validation; logic is correctly ordered and type-guarded.
tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py Adds four mock-based tests covering the absent/partial/explicit/non-admin cases for the new default object_permission merging behavior.

Reviews (2): Last reviewed commit: "fix(proxy): apply default object_permiss..." | Re-trigger Greptile

Comment thread litellm/proxy/management_endpoints/key_management_endpoints.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c4d61f01b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread litellm/proxy/management_endpoints/key_management_endpoints.py Outdated
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Injecting the default before validate_key_vector_stores_against_team /
validate_key_search_tools_against_team ran meant a default containing a
team-scoped field (e.g. vector_stores) looked like a caller-requested
permission, turning ordinary non-admin personal key creation into a 403.
Merge the default into data_json after those checks instead, and guard
against a non-dict default value.
@krrish-berri-2

Copy link
Copy Markdown
Contributor Author

@greptile review

@krrish-berri-2
krrish-berri-2 enabled auto-merge (squash) July 1, 2026 01:59
@krrish-berri-2
krrish-berri-2 merged commit 846dbec into litellm_internal_staging Jul 1, 2026
123 checks passed
@krrish-berri-2
krrish-berri-2 deleted the litellm_default_key_object_permission branch July 1, 2026 02:02
duanhongyi pushed a commit to duanhongyi/litellm that referenced this pull request Jul 2, 2026
…BerriAI#31776)

* feat(proxy): support object_permission in default_key_generate_params

default_key_generate_params filled in a fixed whitelist of scalar fields
plus a full-replace for models/metadata, but never touched object_permission,
so admins had no way to set a default (e.g. mcp_tool_search_enabled,
vector_stores) applied to every new key. Merge object_permission field-by-field
instead of replacing it wholesale, so a caller-supplied field (e.g. mcp_servers)
is preserved alongside defaulted fields the caller left unset.

* ci: retrigger proxy_pass_through_endpoint_tests (suspected flake, unrelated to this PR's diff)

* fix(proxy): apply default object_permission after team-scope validation

Injecting the default before validate_key_vector_stores_against_team /
validate_key_search_tools_against_team ran meant a default containing a
team-scoped field (e.g. vector_stores) looked like a caller-requested
permission, turning ordinary non-admin personal key creation into a 403.
Merge the default into data_json after those checks instead, and guard
against a non-dict default value.
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.

3 participants