mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
feat(proxy): make CORS expose_headers configurable via LITELLM_CORS_EXPOSE_HEADERS
This commit is contained in:
parent
c0ff81a947
commit
55722dabcf
2 changed files with 60 additions and 1 deletions
|
|
@ -1463,7 +1463,30 @@ def _get_cors_config(
|
|||
return computed_origins, computed_credentials
|
||||
|
||||
|
||||
def _get_cors_expose_headers(
|
||||
expose_headers_env: str | None = None,
|
||||
) -> list[str]:
|
||||
"""
|
||||
Compute the CORS Access-Control-Expose-Headers value.
|
||||
|
||||
Defaults to LITELLM_UI_ALLOW_HEADERS. When LITELLM_CORS_EXPOSE_HEADERS is set
|
||||
(comma-separated, parsed like LITELLM_CORS_ORIGINS), its entries are appended to
|
||||
the defaults (de-duplicated, order preserved) so browser clients can read
|
||||
additional x-litellm-* response headers. Unset leaves the default behaviour.
|
||||
|
||||
Args:
|
||||
expose_headers_env: Value of LITELLM_CORS_EXPOSE_HEADERS (defaults to os.getenv).
|
||||
|
||||
Returns:
|
||||
List[str]: headers to expose via Access-Control-Expose-Headers.
|
||||
"""
|
||||
_raw = expose_headers_env if expose_headers_env is not None else os.getenv("LITELLM_CORS_EXPOSE_HEADERS")
|
||||
extra = [h.strip() for h in _raw.split(",") if h.strip()] if _raw else []
|
||||
return list(dict.fromkeys([*LITELLM_UI_ALLOW_HEADERS, *extra]))
|
||||
|
||||
|
||||
origins, allow_cors_credentials = _get_cors_config()
|
||||
cors_expose_headers = _get_cors_expose_headers()
|
||||
|
||||
|
||||
# get current directory
|
||||
|
|
@ -1776,7 +1799,7 @@ app.add_middleware(
|
|||
allow_credentials=allow_cors_credentials,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
expose_headers=LITELLM_UI_ALLOW_HEADERS,
|
||||
expose_headers=cors_expose_headers,
|
||||
)
|
||||
|
||||
app.add_middleware(PrometheusAuthMiddleware)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,42 @@ def test_cors_explicit_credentials_case_insensitive():
|
|||
assert allow_false is False
|
||||
|
||||
|
||||
def test_cors_expose_headers_defaults_to_ui_allow_headers():
|
||||
"""should return exactly LITELLM_UI_ALLOW_HEADERS when the env var is unset/empty."""
|
||||
from litellm.constants import LITELLM_UI_ALLOW_HEADERS
|
||||
from litellm.proxy.proxy_server import _get_cors_expose_headers
|
||||
|
||||
for empty in ("", " ", "\t"):
|
||||
headers = _get_cors_expose_headers(expose_headers_env=empty)
|
||||
assert headers == list(LITELLM_UI_ALLOW_HEADERS), f"Unexpected headers for {repr(empty)}"
|
||||
|
||||
|
||||
def test_cors_expose_headers_appends_extra_headers():
|
||||
"""should append configured headers to the defaults, parsed like LITELLM_CORS_ORIGINS."""
|
||||
from litellm.constants import LITELLM_UI_ALLOW_HEADERS
|
||||
from litellm.proxy.proxy_server import _get_cors_expose_headers
|
||||
|
||||
headers = _get_cors_expose_headers(
|
||||
expose_headers_env=" x-litellm-response-cost , x-litellm-model-api-base ,,"
|
||||
)
|
||||
assert headers == [
|
||||
*LITELLM_UI_ALLOW_HEADERS,
|
||||
"x-litellm-response-cost",
|
||||
"x-litellm-model-api-base",
|
||||
]
|
||||
|
||||
|
||||
def test_cors_expose_headers_dedupes_preserving_order():
|
||||
"""should not duplicate a header already present in the defaults."""
|
||||
from litellm.constants import LITELLM_UI_ALLOW_HEADERS
|
||||
from litellm.proxy.proxy_server import _get_cors_expose_headers
|
||||
|
||||
dup = LITELLM_UI_ALLOW_HEADERS[0]
|
||||
headers = _get_cors_expose_headers(expose_headers_env=f"{dup}, x-litellm-response-cost")
|
||||
assert headers == [*LITELLM_UI_ALLOW_HEADERS, "x-litellm-response-cost"]
|
||||
assert headers.count(dup) == 1
|
||||
|
||||
|
||||
def test_proxy_server_cors_invariant():
|
||||
"""should verify that proxy_server module-level origins and allow_cors_credentials
|
||||
are consistent — catches any future drift in the module-level call to _get_cors_config.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue