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:
parent
d853d3dcd4
commit
08f3c1aae8
@ -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)
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user