From 08f3c1aae82c68f5960bd71e71032d34045b15b2 Mon Sep 17 00:00:00 2001 From: user <70670632+stuxf@users.noreply.github.com> Date: Wed, 13 May 2026 02:52:14 +0000 Subject: [PATCH] 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. --- litellm/proxy/types_utils/utils.py | 20 +++------- .../test_get_instance_fn_runtime_gate.py | 38 +++---------------- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/litellm/proxy/types_utils/utils.py b/litellm/proxy/types_utils/utils.py index 2786564152..8bdaf3fd9e 100644 --- a/litellm/proxy/types_utils/utils.py +++ b/litellm/proxy/types_utils/utils.py @@ -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) diff --git a/tests/test_litellm/proxy/types_utils/test_get_instance_fn_runtime_gate.py b/tests/test_litellm/proxy/types_utils/test_get_instance_fn_runtime_gate.py index fd6b6c6166..bdbc5bc9ba 100644 --- a/tests/test_litellm/proxy/types_utils/test_get_instance_fn_runtime_gate.py +++ b/tests/test_litellm/proxy/types_utils/test_get_instance_fn_runtime_gate.py @@ -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",