Skip to content

chore(release): backport #34189, #36011 to stable/1.94.x and cut 1.94.3 - #36317

Merged
yuneng-berri merged 9 commits into
stable/1.94.xfrom
litellm_backport_1_94_x_bp-194x-0808sec
Aug 8, 2026
Merged

chore(release): backport #34189, #36011 to stable/1.94.x and cut 1.94.3#36317
yuneng-berri merged 9 commits into
stable/1.94.xfrom
litellm_backport_1_94_x_bp-194x-0808sec

Conversation

@yuneng-berri

Copy link
Copy Markdown
Collaborator

Relevant issues

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • 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

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

CI (LiteLLM team)

CI status guideline:

  • 50-55 passing tests: main is stable with minor issues.
  • 45-49 passing tests: acceptable but needs attention
  • <= 40 passing tests: unstable; be careful with your merges and assess the risk.
  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Screenshots / Proof of Fix

Type

🐛 Bug Fix

Changes

Patch release 1.94.3 on top of v1.94.2. Every code commit is a cherry-pick -x of a commit already merged to litellm_internal_staging, so nothing here is new work written against the stable line.

Two upstream PRs are backported. #36011 landed on staging as a real merge commit rather than a squash, so there is no single SHA to pick; its six constituent commits are picked individually. That was verified equivalent to cherry-pick -m 1 of the merge — the merge diff (c898d341^1..c898d341) and the branch range diff (f047124b..298fb8ce) share the identical patch-id 8979711cefee729f, so the merge absorbed no staging drift.

Backported Staging commit Notes
chore(proxy): clean up request parameter validation and provider destination handling (#34189) 065faf6e69 adapted, see below
fix(proxy)!: share one destination check between body and path-supplied model (#36011) fc4be70a37 adapted, see below
fix(proxy)!: parse bracket-notation form metadata the same way its JSON form is parsed (#36011) 5b2c92d749 applies verbatim
fix(health)!: let configured deployment parameters win over request overrides (#36011) e5effcb861 adapted, see below
fix(health): stop inheriting configured credentials when a connection test sets its own (#36011) b468acb31c adapted, see below
feat(health): let allow_client_side_credentials re-enable configured-credential reuse (#36011) 59173c3a20 applies verbatim
fix(health): drop a stored-credential reference along with the credentials it names (#36011) 298fb8ce56 applies verbatim

Plus bump: version 1.94.2 → 1.94.3 and chore: refresh uv.lock for 1.94.3.

Ordering is mandatory: #36011 calls iter_request_fallback_targets and reject_url_valued_destination, both introduced by #34189 and neither present on the line before it.

Also requested, already on the line — no pick needed

Nine PRs were requested for this patch. Seven were already present and were verified rather than re-picked:

PR Route onto the line
#30585, #30867, #31905, #32093, #32405 merged to staging before the line's cut (merge-base 5d4c4d0fce, 2026-07-18), so carried in at the cut
#35835, #35844 backported earlier in #36237, merged 2026-08-08

⚠️ Behavior changes

#36011 is a breaking change and this patch carries it. Each is the hardening itself and cannot be opted out of individually, but each has a documented way to restore the prior behavior where it was intentional.

  1. A deployment name supplied in the request path that parses as an http/https destination is now refused. Add the host to provider_url_destination_allowed_hosts under litellm_settings to keep it working.
  2. A multipart field such as litellm_metadata[api_base] is now subject to the same request-body parameter rules as its JSON equivalent. Set general_settings.allow_client_side_credentials, or the deployment's configurable_clientside_auth_params, to keep passing these.
  3. /health/test_connection no longer lets a request replace the routing or credential parameters of a configured model it names. Supply the full connection parameters instead of naming a configured model, or set general_settings.allow_client_side_credentials: true to restore the previous merge behavior unchanged.

Naming a configured model and testing it as configured is unchanged — that is how the Admin UI tests a model, and that flow is explicitly covered by a regression assertion.

Worth flagging for risk assessment: #36011 has never shipped in a GA release (only v1.97.0-dev.2). #34189 shipped in v1.95.0.

Adaptation notes

Four of the seven picks needed adaptation. All four are context divergence from staging lint/typing migrations that post-date the line's cut. None changes behavior.

  • #34189tests/code_coverage_tests/recursive_detector.py. Staging's IGNORE_FUNCTIONS contains _redact_scanned_content from an unrelated PR, and no such function exists on this line. Only the entry #34189 actually contributes, _iter_fallback_targets, was added.
  • fc4be70a37litellm/proxy/litellm_pre_call_utils.py. The deletion hunk targeted allowed_hosts: Final = getattr(...); this line's copy has no Final (staging's enforce-Final-on-locals migration post-dates the cut). Resolved against the line's form, and Final added to that module's typing import for the new reject_url_valued_destination(). model: Optional[str] also retained over staging's model: str | None in image_endpoints/endpoints.py.
  • e5effcb861litellm/proxy/health_endpoints/_health_endpoints.py. Staging imports Iterable from collections.abc; this line keeps it in typing. Mapping was added to the line's existing typing import instead. Load-bearing: Mapping appears in the _reject_banned_param_overrides signature, which is evaluated at definition time, so the module would not have imported without it.
  • b468acb31c — same file. Introduces _CONFIG_CONNECTION_FIELDS: Final[frozenset[str]] at module level, where annotations are evaluated at runtime. This hunk cherry-picked with zero conflict, and without Final in the import the proxy would have failed to boot with NameError on import. Final was added and the pick amended so the fix sits in the commit that needs it.

The last one is the notable catch: git reported no conflict and the pick-owned tests would not have caught it. Only importing every touched module under the branch's own interpreter did.

Verification

Symbol closure. Every touched module imported for real in the branch's venv; reject_url_valued_destination, iter_request_fallback_targets, provider_url_destination_candidates, _config_base_for_health_check and _CONFIG_CONNECTION_FIELDS all resolve. New paths checked for reachability, not just existence — image_endpoints/endpoints.py imports the guard at :73 and calls it at :101.

Tests owned by the picks (7 files on the branch, the 6 that exist on the base):

Tree Result
This branch 387 passed, 0 failed
stable/1.94.x tip 317 passed, 0 failed

Blast radiustests/test_litellm/proxy, two passes on each tree:

Tree Pass 1 Pass 2
This branch 50 failed / 13809 passed 45 failed / 13814 passed
stable/1.94.x tip 45 failed / 13747 passed 51 failed / 13742 passed

Pass 1 showed +5 on the branch; that is xdist --dist loadfile order dependence, not regression. All 8 branch-only failures pass in isolation on the branch, all 3 base-only failures pass in isolation on the base, and re-running the identical command flipped the counts — on pass 2 the untouched base is worse than the branch.

Format and lint, run under this line's own config: ruff format --check reports the same single pre-existing offender (proxy/management_endpoints/credential_migration.py) on both trees, and ruff check --statistics is byte-identical at 1017 errors across the same 14 rule codes. Zero delta on both.

Behavior, red/green. A standalone reproducer run under each tree's interpreter:

Claim v1.94.2 This branch
Destination check covers path-supplied model absent — helper does not exist refused with HTTPException; allowlist re-opens it
Bracket-notation form metadata checked like JSON slipped through unchecked both encodings refused identically
Connection test withholds configured credentials absent — helper does not exist secrets withheld; as-configured path intact; opt-in restores merge

The middle row is the strongest: on v1.94.2 both symbols exist and the code path is fully reached — the check simply fails to block metadata[api_base] while blocking its JSON twin. That is the issue reproduced live rather than inferred from a missing symbol.

Dependencies. No dependency moves in this patch. A grype sweep of the lock reports 0 Critical / 0 High / 3 Medium on the base, on litellm_internal_staging, and on this branch — identical at every stage. The two in-range pypdf 6.14.2 advisories (GHSA-fp3f-mc75-235c, GHSA-fwg2-594c-jp42, fixed in 6.15.0) are deliberately not bumped: staging also resolves 6.14.2, so moving the stable line alone would put it ahead of mainline and a customer upgrading off this patch would regress back into both. This ships at staging parity by design. diskcache 5.6.3 has no fixed version released.

uv.lock moves by exactly two lines — the litellm version and the exclude-newer stamp that exclude-newer = "3 days" re-materializes on every lock. Zero package version drift.

Release notes for v1.94.3 are drafted and will follow in a separate litellm-docs PR.

yucheng-berri and others added 9 commits August 8, 2026 13:06
…ed model

The URL-destination check previously ran over request-body fields only. The
per-field logic moves into reject_url_valued_destination(field, value) so a
deployment name resolved from the request path runs the same check against the
same admin allowlist.

BREAKING CHANGE: a deployment name supplied in the request path that parses as
an http/https destination is now refused. Add the host to
`provider_url_destination_allowed_hosts` in litellm_settings to keep it working.

(cherry picked from commit fc4be70)
…ON form is parsed

Multipart callers express nested metadata as flat bracket-notation keys, which
reach the request-body check as literal keys rather than as a metadata dict.
The check now rebuilds them with the same helper the endpoints use, so both
encodings are handled identically and cannot drift apart.

BREAKING CHANGE: a multipart field such as `litellm_metadata[api_base]` is now
subject to the same request-body parameter rules as its JSON equivalent. Set
`general_settings.allow_client_side_credentials`, or the deployment's
`configurable_clientside_auth_params`, to keep passing these.

(cherry picked from commit 5b2c92d)
…verrides

When a connection test names a model that resolves to a configured deployment,
that deployment's routing and credential parameters are authoritative. A request
supplying a complete connection of its own is unaffected.

BREAKING CHANGE: /health/test_connection no longer lets a request replace the
routing or credential parameters of a configured model it names. Supply the full
connection parameters instead of naming a configured model.

(cherry picked from commit e5effcb)
… test sets its own

A request that supplies its own connection fields describes a connection of its
own, so the configured deployment's credentials are no longer merged underneath
it. Anything the request leaves unset still comes from the configuration, so
naming a configured model and testing it as configured is unchanged, and adding
a second deployment for an already-configured name works as before.

Replaces the earlier outright rejection, which also refused requests that
supplied a complete connection of their own.

(cherry picked from commit b468acb)
…credential reuse

The proxy-wide opt-in that already governs callers supplying their own
connection parameters now also governs whether a connection test may pair a
request-supplied endpoint with the configured deployment's credentials. Off by
default, which keeps configured credentials scoped to the endpoint the
configuration names; on, the previous merge behaviour is available unchanged.

(cherry picked from commit 59173c3)
…tials it names

A connection test that redirects the destination already leaves the configured
credentials behind. It kept litellm_credential_name, which names the same stored
secrets and is resolved further down the call, so the reference is now dropped
with them. A request that sets no connection fields of its own is unaffected,
which is how the Admin UI tests a configured model.

(cherry picked from commit 298fb8c)
@yuneng-berri
yuneng-berri requested a review from a team August 8, 2026 23:23
@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This patch cuts LiteLLM 1.94.3 and backports request-destination, fallback-validation, multipart metadata, and health-check credential hardening

  • Centralizes URL-valued destination checks across body, path, image, and fallback model inputs
  • Shares recursive fallback traversal between request safety and key-level model authorization
  • Prevents configured health-check credentials from being combined with caller-supplied connection overrides unless explicitly enabled
  • Corrects URL detection for Hugging Face embedding and Oobabooga model names
  • Updates release metadata and adds regression coverage

Confidence Score: 4/5

The PR appears safe to merge after addressing the non-blocking source-comment convention violation

The changed request validation, authorization traversal, provider URL handling, and health-check credential logic have no established blocking failure; the retained pypdf advisories predate this patch

Files Needing Attention: litellm/proxy/health_endpoints/_health_endpoints.py

Important Files Changed

Filename Overview
litellm/proxy/auth/auth_utils.py Adds shared recursive fallback traversal, nested multipart metadata validation, Vertex credential filtering, and URL-valued fallback rejection
litellm/proxy/auth/user_api_key_auth.py Reuses the shared fallback traversal for key-level model authorization without a confirmed authorization regression
litellm/proxy/health_endpoints/_health_endpoints.py Separates configured connection credentials from request overrides, but adds source documentation contrary to the repository convention
litellm/proxy/litellm_pre_call_utils.py Extracts a reusable destination guard that recognizes provider-prefixed and comma-separated URL candidates
litellm/proxy/common_request_processing.py Applies destination validation to path-supplied model values before normal request routing
litellm/proxy/image_endpoints/endpoints.py Extends the path-model destination guard to image generation
litellm/litellm_core_utils/url_utils.py Adds candidate extraction for raw, provider-prefixed, and comma-separated destination values
litellm/llms/huggingface/embedding/handler.py Restricts direct URL detection to actual HTTP and HTTPS prefixes
litellm/llms/oobabooga/chat/oobabooga.py Restricts direct URL detection to actual HTTP and HTTPS prefixes for chat and embedding calls
uv.lock Refreshes release metadata without changing dependency versions, including the pre-existing pypdf version

Reviews (1): Last reviewed commit: "chore: refresh uv.lock for 1.94.3" | Re-trigger Greptile

Comment on lines +107 to +126


def _config_base_for_health_check(
config_params: Mapping[str, object],
request_params: Mapping[str, object],
allow_client_side_credentials: bool = False,
) -> dict[str, object]:
"""Return the configured parameters to merge under a connection-test request.

A request that sets its own connection fields describes a connection of its
own, so the configuration's credentials are not carried into it: they belong
to the endpoint the configuration names. Anything the request does not set
still comes from the configuration, which is what lets a request name a
configured model and test it as configured.

``litellm_credential_name`` is dropped alongside the literal credential
fields: it names a stored credential that ``load_credentials_from_list``
resolves into the same secrets further down the call, so leaving it in place
would reintroduce them by reference.

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 New source documentation violates convention

The new multi-paragraph docstring, along with similar explanatory additions elsewhere in this patch, violates the repository rule against adding source-code comments and increases maintenance prose that the implementation must keep synchronized.

Context Used: CLAUDE.md (source)

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 merged commit 397859f into stable/1.94.x Aug 8, 2026
6 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_backport_1_94_x_bp-194x-0808sec branch August 8, 2026 23:34
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