fix(proxy): always merge caller-supplied tags into request metadata - #27789
Conversation
Caller-supplied tags (`x-litellm-tags` header, body `tags`, `metadata.tags`) were silently dropped unless the key/team had `metadata.allow_client_tags: true` set. Restore the documented behavior: tags from the request always flow into `metadata.tags` and union with any admin-configured static tags from key/team/project metadata. Removes the `allow_client_tags` opt-in flag from the pre-call pipeline. The flag was only ever read here; it has no schema or endpoint footprint, so leftover values in existing key metadata are inert. Test cleanup mirrors the simplification: drop the three tests that verified the strip-when-not-opted-in path, drop the `allow_client_tags` fixture lines from the merge/union tests.
The tag-strip block was removed in the parent commit but two surrounding comments still referenced "tags without opt-in" and "runs AFTER the strip". Update them to describe the remaining user_api_key_* and _pipeline_managed_guardrails strip that the snapshot/merge ordering actually protects against.
| "Ignored caller-supplied tags from header/root body: this " | ||
| "key/team does not have `allow_client_tags: true` in its metadata." | ||
| if tags is not None: | ||
| data[_metadata_variable_name]["tags"] = LiteLLMProxyRequestSetup._merge_tags( |
There was a problem hiding this comment.
High: Client-controlled routing tags
Any authenticated caller can now add x-litellm-tags or root-level tags and have them merged into the metadata used by tag-based routing; because the strip above was removed, metadata.tags and litellm_metadata.tags also survive. A user who knows a restricted deployment tag can route their request to that tagged deployment and have spend recorded under arbitrary tag names, so keep caller-supplied tags behind an explicit admin opt-in or only merge key/team/project tags by default.
High: Client-controlled tags can select tagged deploymentsThis PR removes the opt-in gate around request tags and merges caller-provided values into routing metadata. A normal authenticated user can now supply a deployment tag in the request and have the router treat it as authoritative. Status: 1 new · 1 open |
Greptile SummaryThis is a backport of #27784 onto
Confidence Score: 4/5Safe to merge as a regression fix; the only concern is that removing the flag also removes any admin opt-out path for deployments that were relying on the default-off behavior. The core implementation is correct — caller tags are unioned with admin-static tags through the existing
|
| Filename | Overview |
|---|---|
| litellm/proxy/litellm_pre_call_utils.py | Removes the allow_client_tags gate entirely; caller-supplied tags from header, root body, and metadata.tags now unconditionally flow into request metadata and union with admin-static tags from key/team/project. |
| tests/test_litellm/proxy/test_litellm_pre_call_utils.py | Three "strip-without-permission" tests converted to positive coverage of union/dedup behavior; two "allow_client_tags opt-in" tests removed as the condition no longer exists; remaining assertions correctly cover the new unconditional-merge path. |
| tests/proxy_unit_tests/test_proxy_utils.py | Removes allow_client_tags: True fixture lines from spend-log-metadata and duplicate-tags tests; the core assertions about tag merging/union continue to hold unchanged. |
Reviews (1): Last reviewed commit: "docs(proxy): refresh stale comments refe..." | Re-trigger Greptile
| ", ".join(_stripped_from), | ||
| ) | ||
|
|
||
| # Fill in the proxy_server_request body snapshot now that metadata has |
There was a problem hiding this comment.
No opt-out path for tag injection after
allow_client_tags removal
The allow_client_tags guard is removed entirely, so any caller can now supply x-litellm-tags, data["tags"], or metadata.tags and have them unconditionally merged into request metadata. There is no longer any admin-controlled way to prevent this. Admins who were relying on the default-off behaviour (e.g., to stop callers from misattributing spend to another team's tag budget, or from reaching specific tag-routed deployments) have no replacement mechanism after upgrading. Per the backwards-compatibility rule, a safer approach would be to flip the default to True while keeping the flag readable, so existing allow_client_tags: false deployments retain their restriction without requiring a code change.
Rule Used: What: avoid backwards-incompatible changes without... (source)
Backport of #27784 onto
litellm_1.84.0rc2.Summary
Caller-supplied tags (
x-litellm-tagsheader, bodytags,metadata.tags) were silently dropped from request metadata unless the calling key/team hadmetadata.allow_client_tags: trueset. This broke two documented features:x-litellm-tags— the documented per-request header for routing to tagged deployments.request_tagsgoing into spend logs and/spend/tagsaggregations.This PR restores the previous behavior: tags from the request always flow into
metadata.tagsand union with any admin-configured static tags from key/team/project metadata.What changed
litellm/proxy/litellm_pre_call_utils.py— drop the conditional strip of callertagsfrom body /metadata/litellm_metadataand the conditional gate on thex-litellm-tagsheader merge. Both now run unconditionally.allow_client_tagsflag is removed from the pre-call pipeline. It was only ever read here — no schema, types, or endpoint footprint — so existing values in key/team metadata are inert.Test changes
"allow_client_tags": Truefixture lines from the merge/union/multipart tests; their assertions about union behavior continue to hold.Test plan
uv run pytest tests/test_litellm/proxy/test_litellm_pre_call_utils.py— 117 passeduv run pytest tests/proxy_unit_tests/test_proxy_utils.py -k "spend_logs_metadata or duplicate_tags"— 69 passeduv run pytest tests/test_litellm/router_strategy/test_router_tag_routing.py tests/test_litellm/proxy/auth/test_auth_checks.py— 112 passedEnd-to-end verification was performed on the parent PR (#27784) against a running proxy.