Flatten dict-backed multipart bodies so a scalar list becomes one field with a
tuple value, which httpx emits as a repeated part per element, instead of
collapsing to the last element under dict.update. Nested objects still flatten
to key[subkey] like the OpenAI SDK, and the file-tuple video path is untouched.
The hand-rolled drop set missed Ocp-Apim-Subscription-Key, so a caller
Azure APIM secret in that header was forwarded to Google on the
credential-less branch. Derive the name-drop set from the canonical
SpecialHeaders.litellm_credential_header_names(), minus Authorization and
x-goog-api-key which double as real Google credentials and are value-stripped
instead. New credential headers added there are now dropped automatically.
Uploaded files reaching the RAG ingest path were trusted by client
filename and content-type, so archives and executable scripts were
ingested and malicious content was never screened. Enforce controls at
the upload boundary before the file leaves the proxy:
- classify content by magic bytes and a strict UTF-8 decode, never by
the client filename or content-type
- allowlist PDF and UTF-8 text; reject archives and executables/scripts
- cap upload size (512MB) via a bounded read
- run every accepted upload through a dependency-injected malware
scanner, failing closed on scan error; the default scanner flags the
EICAR test file so the hook is validated end to end
- give accepted uploads a server-generated filename so the client name
never reaches storage
- set Content-Disposition attachment and X-Content-Type-Options nosniff
on vector-store file downloads
The router hop _ageneric_api_call_with_fallbacks canonicalises the passthrough
call type onto litellm_metadata, and the cost callback reads spend attribution
from that bucket while only backfilling user_api_key* keys from metadata. The
helper was building on metadata, so agent_id and user_api_end_user_max_budget
were silently dropped before the callback ever saw them. Build and pass the
attribution under litellm_metadata so every field survives.
log_retry copied every kwarg into the previous_models breadcrumb, so a client's
forwarded Authorization (provider_specific_header) and the deployment api_key /
headers rode along in an in-memory structure whose comment says it reaches spend
logs and logging callbacks. Those values have no diagnostic use in a breadcrumb.
Add provider_specific_header, headers, and api_key to RETRY_BREADCRUMB_EXCLUDED_KWARGS
so the credential is never placed there in the first place. This is defense in depth:
no persisted leak exists today, since the SpendLogs metadata allowlist and every
logging integration already drop previous_models before serialization. Removing the
credential at the source means a future logging path cannot expose it either
user_api_key_auth also authenticates a caller from the operator-configured
general_settings.litellm_key_header_name, reading that header straight off
the request, so a virtual key sent there survived the credential-less Vertex
forwarding filter and reached Google alongside a real bring-your-own
credential. Value-strip every header whose value matches the caller's key
from any accepted source, including that custom header.
- send a caller api_key via the Azure api-key header instead of Authorization: Bearer
- cap web_search results to the requested max_results (the tool has no count knob)
- surface a Foundry failed/incomplete response status as a 502 error
- zero the per-query cost in web_search mode; keep the map price for connection mode
- trim the example config to terse env-var pointers
The claude branch called the anthropic SDK's `Anthropic().count_tokens`, which the
SDK removed, so every claude call raised AttributeError. Counting now goes through
litellm's own token_counter, which handles anthropic models offline and drops the
SDK dependency entirely.
Hiding that was a swallowed error: `except Exception: Exception("Anthropic import
failed please run `pip install anthropic`")` built the exception without raising
it, so an environment missing the SDK fell through to the unguarded
`from anthropic import ...` on the next line and got a bare ModuleNotFoundError
instead of the install hint.
That was the codebase's last PLW0133, so the rule graduates from the ratcheted
budget into ruff.toml where it hard-fails, and editors get the diagnostic inline.
On the credential-less Vertex passthrough branch, drop every header that
can only carry LiteLLM caller auth (x-litellm-api-key, api-key, x-api-key)
by name, since Google never consumes them, and strip the virtual key by
value from Authorization / x-goog-api-key, which may instead hold a genuine
bring-your-own Google credential. This closes the residual leak where a
distinct caller secret in api-key or x-api-key still reached upstream.
Adds a regression asserting the value-based strip also drops the caller's
virtual key when it is duplicated into the api-key and x-api-key headers,
while a genuine bring-your-own Google credential still forwards.
Add an endpoint-level regression test asserting can_user_make_model_call
receives the litellm_params after health_check_params are merged in, so the
merge-before-auth ordering cannot silently regress and let a request smuggle
a field past authorization.
* fix(ui): boot the UI image as an arbitrary uid by anchoring nginx writes under /tmp
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(ui): type the arbitrary-uid image test fixture
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: yassin <yassin@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
AzureVideoConfig subclasses OpenAIVideoConfig and so inherits the new
use_multipart_form_data() -> True. Azure's /openai/v1/videos surface is
OpenAI-SDK-compatible, so the JSON->multipart flip is intentional; assert it
through the real handler so the inherited behavior can't silently regress.
The openai/azure/compat image-edit funnel merged non_default_params and
extra_body straight into the multipart body, so a nested value (e.g.
extra_body={"metadata": {...}}) reached the httpx encoder and 500'd with
"Invalid type for value. Expected primitive type". Route the funnel through
a shared flattener that serializes nested values as OpenAI-SDK bracket fields
(key[subkey], lists as key[], bools lowercased, None/empty dropped), matching
the wire format of the rest of this fix.
The credential-less Vertex passthrough dropped the caller's LiteLLM
virtual key only from Authorization by exact match. A caller who sent
the same key in x-goog-api-key (which doubles as a real Google
credential) had it accepted as a credential and forwarded upstream.
Drop the virtual key by value across every forwarded header, normalizing
any Bearer prefix, so no header name carries it to Google.
Regenerate model_prices_and_context_window.schema.json and add the flag to
the inline validator schema in test_utils.py so the new cost-map key passes
validate-model-prices-json and the JSON-valid test.
Call detection restored a single-quoted DO or EXECUTE payload through
without_comments while its `''` escapes were still doubled. The first quote of
a pair opened an empty string and closed it on the second, leaving a `--` or
`/*` from a nested string bare, so it blanked the real call after it and the
routine read as uncalled: its rewrite body then went unscanned at boot. Undouble
each single-quoted payload before restoring it, and pad it back to the span it
fills so the later offsets still land. Dollar-quoted bodies do not escape quotes
and are left as they were.
* fix(anthropic): reconcile enum with declared type in output_format schema
Anthropic cross-validates `enum` against `type` in structured outputs: every
enum value must match a single declared type. A union `type` array, or an enum
value whose JSON type differs from a scalar `type`, is rejected with
"Invalid schema: Enum value 'low' does not match declared type '['string','null']'"
filter_anthropic_output_schema had no enum/type reconciliation, so both keys
reached Anthropic untouched. Drop the conflicting `type` -- `enum` is the
tighter constraint, and an enum with no `type` is accepted
The drop is conditional: `type` is only removed when it is a union array, or
when some enum value does not match the scalar type. A matching enum plus
scalar `type` is left exactly as-is, so existing behaviour is unchanged
Pydantic emits the failing shape for Optional[SomeEnum], so this affects any
caller with a nullable enum field on the native output_format path. vertex_ai
is unaffected because it is forced onto the permissive tool-use path
Fixes#37881
* refactor(anthropic): make enum/type reconciliation immutable and precisely typed
Address review: the predicate registry was a mutable `dict[str, Any]`, and the
reconciliation removed `type` by mutating the built result with `pop`
- registry is now `Final[Mapping[str, Callable[[Any], bool]]]` wrapped in
`MappingProxyType`, so predicate signatures are statically checked and the
table cannot be mutated
- the conflict decision moves into a pure helper evaluated once against the
input schema, and the conflicting `type` key is skipped at build time in the
existing loop instead of being popped afterwards, so nothing is mutated
Behaviour is unchanged; all 27 tests in the schema-filter suite still pass
Bring the Entra ID / OAuth auth work for Azure AI Foundry routes up to date
with staging and fix the lint-budget regressions the merge surfaced:
- widen get_azure_ai_auth_headers return type to Mapping[str, str] (LIT001)
- build the azure_ai image_generation request headers into a new Final local
instead of rebinding the Final headers dict (reportGeneralTypeIssues)
- order HuggingFace rerank validate_environment params to match BaseRerankConfig
so litellm_params lines up positionally (reportIncompatibleMethodOverride)
- add a match= to the credential-error test and document the handler-boundary
patches the auth wiring tests rely on
Pulls in the detect-changes CI action and the test-unit job timeout bump, which clears the red lint and code-quality checks on this PR
The merged, tightened lint budgets flag this PR's own code, so this merge also makes video_reference_to_id a pure function instead of a helper that mutates its input dict, and rewrites the form-body regression test to call the video_edit and video_extension handlers directly rather than patching an internal class method. Adds pure-logic unit tests for video_reference_to_id
Move _update_litellm_params_for_health_check before can_user_make_model_call
so health_check_params cannot retarget the probe after the auth check. Type
the Pegasus test helper signature and drop the redundant test narrative.
Six defects in the RunwayML video provider:
- transform_video_create_request hardcoded /image_to_video, so text-to-video 400'd and video-to-video was unreachable; the endpoint is now selected from the inputs present (promptVideo/videoUri, promptImage, or text only)
- get_error_class raised instead of returning, turning a provider 4xx into a proxy 500 APIConnectionError; it now returns a RunwayMLError
- VideoObject.progress was typed int while Runway sends a 0..1 float, 500'ing status polls while RUNNING; it is now scaled to a 0..100 percent
- custom per-deployment pricing stored under litellm_metadata was ignored for video; the deployment model_info lookup now checks both metadata keys
- stale cost-map entries (gen3a_turbo, gen4_aleph) were removed and current models added, with output_cost_per_second_480p/_4k tier keys plumbed through the model-info and router types
- video cost now falls back to Runway's estimatedCost from the create response when no custom pricing is configured, and custom pricing always wins over it
Fixes#36483
When no Vertex credential is configured (no default_vertex_config, no matching
use_in_pass_through deployment, no vector-store credential), the Vertex passthrough
took the bring-your-own-credentials branch and forwarded the entire incoming header
set upstream to Google. That set included whichever header carried the caller's
LiteLLM virtual key: x-litellm-api-key, or Authorization when get_litellm_virtual_key
read the key from there. The proxy's own secret was sent to a third-party provider.
The credential-less branch now drops x-litellm-api-key and the Authorization value
that equals the virtual key, keeping a genuine bring-your-own Google credential
(an OAuth token in Authorization, or x-goog-api-key) so real BYO passthrough still
works. When neither survives, the request fails with a clean 401 telling the operator
no credential is configured, instead of forwarding the virtual key.
Regression coverage in the mapped test path asserts the 401-and-never-forwarded
behavior for both leak vectors and that a real Google credential still passes through
with the virtual key stripped.
The /vllm and /azure router-model passthrough branches called
llm_router.allm_passthrough_route directly with no request metadata,
so the cost callback saw no user_api_key and no
user_api_key_budget_reservation. Spend for a budgeted virtual key hit
neither the key's spend nor the spend logs, and the reservation minted
at auth into the shared Redis counter was never released, drifting the
counter up until the key falsely tripped BudgetExceededError.
Thread the authenticated key's attribution metadata into both calls via
the same builder add_litellm_data_to_request uses, so the cost callback
attributes spend and reconciles the reservation. Regression tests cover
both branches.
POST /v1/videos without an input_reference file now goes out as
multipart/form-data the way the OpenAI SDK always sends it, instead of a
JSON body that OpenAI-compatible backends (SGLang Diffusion, vLLM-Omni)
reject; gemini, vertex, and runwayml keep their JSON bodies
/v1/images/edits on the openai/azure/openai-compatible path now forwards
unknown provider params (e.g. seed) and honors extra_body, matching
/v1/images/generations, and aimage_edit forwards
extra_headers/extra_query/extra_body instead of dropping them
Generic pass-through no longer downgrades a file-less multipart form to
application/x-www-form-urlencoded