fix(types_utils): drop opt-in env from remote-module runtime gate

The runtime gate on s3://gcs:// loading in get_instance_fn previously
allowed an opt-in via LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API. That
env var is admin-flippable at runtime (DB-overlay environment_variables
flow into os.environ), which defeats the gate's purpose, and it isn't
needed for the documented operator flow: config.yaml callbacks always
pass config_file_path through to the loader.

Remove the helper, raise unconditionally when config_file_path is None,
and drop the corresponding test for the opt-in branch.
This commit is contained in:
user 2026-05-13 02:52:14 +00:00
parent d853d3dcd4
commit 08f3c1aae8
No known key found for this signature in database
2 changed files with 10 additions and 48 deletions

View File

@ -5,14 +5,6 @@ import os
from typing import Any, Callable, Literal, Optional, get_type_hints
def _allow_remote_instance_fn_from_api() -> bool:
return os.getenv("LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API", "").lower() in (
"1",
"true",
"yes",
)
def get_instance_fn(value: str, config_file_path: Optional[str] = None) -> Any:
module_name = value
instance_name = None
@ -23,17 +15,15 @@ def get_instance_fn(value: str, config_file_path: Optional[str] = None) -> Any:
# invoked from config-file load (``config_file_path`` carries
# the YAML path). Without that signal the URL is request-body
# data on an admin endpoint — a one-step admin-to-RCE primitive
# via ``_load_instance_from_remote_storage``'s
# ``exec_module``. Refuse unless the operator explicitly opts
# back in via ``LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API``.
if config_file_path is None and not _allow_remote_instance_fn_from_api():
# via ``_load_instance_from_remote_storage``'s ``exec_module``.
# Register the module under ``litellm_settings`` in the
# config.yaml instead.
if config_file_path is None:
raise ValueError(
"Remote module loading (s3://, gcs://) is only "
"permitted from the config-file load path. Register "
"the module under ``litellm_settings`` in your "
"config.yaml instead. To opt into runtime API "
"loading, set "
"``LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API=true``."
"config.yaml instead."
)
return _load_instance_from_remote_storage(value, config_file_path)

View File

@ -1,8 +1,6 @@
"""
Regression tests: ``get_instance_fn`` refuses remote module loading
(``s3://``, ``gcs://``) when invoked without a ``config_file_path``
unless the operator explicitly opts in via
``LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API``.
(``s3://``, ``gcs://``) when invoked without a ``config_file_path``.
The startup config-file load path passes ``config_file_path`` and is
unaffected the documented ``litellm_settings.callbacks:
@ -22,46 +20,20 @@ sys.path.insert(
from litellm.proxy.types_utils.utils import get_instance_fn # noqa: E402
@pytest.fixture(autouse=True)
def _strip_opt_in_env(monkeypatch):
monkeypatch.delenv("LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API", raising=False)
@pytest.mark.parametrize(
"scheme",
["s3", "gcs"],
)
@pytest.mark.parametrize("scheme", ["s3", "gcs"])
def test_remote_url_without_config_file_path_is_rejected(scheme):
# The C1-Stage-B attack vector: admin endpoint POSTs an s3:// /
# gcs:// instance specifier via the request body; no
# The C1-Stage-B attack vector: admin endpoint receives an
# s3:// / gcs:// instance specifier via the request body; no
# ``config_file_path`` is in scope. Must refuse before the
# ``exec_module`` sink is reached.
with pytest.raises(ValueError, match="Remote module loading"):
get_instance_fn(value=f"{scheme}://attacker-bucket/module.instance")
@pytest.mark.parametrize("scheme", ["s3", "gcs"])
def test_remote_url_with_opt_in_env_is_allowed_to_reach_loader(scheme, monkeypatch):
# When the operator explicitly opts in, the runtime path is allowed
# to reach ``_load_instance_from_remote_storage`` — which then makes
# its own decisions / fails on AWS auth / etc. We only verify the
# gate doesn't fire before the loader is reached.
monkeypatch.setenv("LITELLM_ALLOW_REMOTE_INSTANCE_FN_FROM_API", "true")
with patch(
"litellm.proxy.types_utils.utils._load_instance_from_remote_storage",
return_value="loaded",
) as mock_loader:
result = get_instance_fn(value=f"{scheme}://my-bucket/m.inst")
assert result == "loaded"
mock_loader.assert_called_once_with(f"{scheme}://my-bucket/m.inst", None)
def test_remote_url_with_config_file_path_is_allowed():
# Startup config-file load path: ``config_file_path`` is set, so
# the gate doesn't fire. Documented operator feature must keep
# working without the opt-in env.
# working.
with patch(
"litellm.proxy.types_utils.utils._load_instance_from_remote_storage",
return_value="loaded",