From 3aef0642a80b604c76cacb59ff590db59ba4991b Mon Sep 17 00:00:00 2001 From: user <70670632+stuxf@users.noreply.github.com> Date: Thu, 14 May 2026 00:32:01 +0000 Subject: [PATCH] chore(proxy): also scrub pass_through_endpoints[].target from DB overlay A pass-through endpoint's ``target`` field is passed through ``create_pass_through_route`` into ``get_instance_fn`` during config load. A PROXY_ADMIN persisting ``target: "s3://attacker/m.i"`` via the DB-overlay ``pass_through_endpoints`` write path was not covered by the previous scrub matrix, so the remote module load would still reach the loader because the YAML-load chain has ``config_file_path`` set. Walk each entry in ``general_settings.pass_through_endpoints`` and null out any ``target`` that starts with ``s3://`` or ``gcs://``. The entry itself is preserved so the path-registration helper can choose how to handle a missing target (the existing code skips the route when ``target is None``). Adds two regression tests. --- litellm/proxy/proxy_server.py | 19 ++++++++++++ .../test_db_overlay_remote_module_scrub.py | 29 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index f7cb767d89..2a512f58db 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3203,6 +3203,25 @@ def _scrub_db_overlay_remote_module_loads(section: str, db_value: Any) -> Any: jwt.get("custom_validate"), ) jwt["custom_validate"] = None + # ``pass_through_endpoints`` is a list of dicts whose ``target`` + # is passed through ``create_pass_through_route`` → + # ``get_instance_fn``. A DB-overlay ``target: "s3://attacker/m.i"`` + # would otherwise reach the loader because the YAML-load chain + # has ``config_file_path`` set. + pte = sanitized.get("pass_through_endpoints") + if isinstance(pte, list): + for entry in pte: + if isinstance(entry, dict) and _is_remote_module_url( + entry.get("target") + ): + verbose_proxy_logger.warning( + "Refused remote-URL target from DB-overlay " + "general_settings.pass_through_endpoints " + "(path=%r): %r", + entry.get("path"), + entry.get("target"), + ) + entry["target"] = None return sanitized diff --git a/tests/test_litellm/proxy/types_utils/test_db_overlay_remote_module_scrub.py b/tests/test_litellm/proxy/types_utils/test_db_overlay_remote_module_scrub.py index 01ece825f0..66498a45a2 100644 --- a/tests/test_litellm/proxy/types_utils/test_db_overlay_remote_module_scrub.py +++ b/tests/test_litellm/proxy/types_utils/test_db_overlay_remote_module_scrub.py @@ -71,6 +71,35 @@ def test_custom_provider_map_custom_handler_stripped(): assert cleaned["custom_provider_map"][1]["custom_handler"] is None +def test_pass_through_endpoints_target_stripped(): + overlay = { + "pass_through_endpoints": [ + {"path": "/ok", "target": "my_module.legit_handler"}, + {"path": "/bad-s3", "target": "s3://attacker/m.handler"}, + {"path": "/bad-gcs", "target": "gcs://attacker/m.handler"}, + ] + } + cleaned = _scrub_db_overlay_remote_module_loads("general_settings", overlay) + # Legit dotted-name target preserved + assert cleaned["pass_through_endpoints"][0]["target"] == "my_module.legit_handler" + # Both remote URLs stripped to None — entry remains so the path + # registration can still be skipped explicitly downstream + assert cleaned["pass_through_endpoints"][1]["target"] is None + assert cleaned["pass_through_endpoints"][2]["target"] is None + # Sibling fields preserved + assert cleaned["pass_through_endpoints"][0]["path"] == "/ok" + assert cleaned["pass_through_endpoints"][1]["path"] == "/bad-s3" + + +def test_pass_through_endpoints_non_list_passthrough(): + # If pass_through_endpoints is mistyped (not a list), the scrub + # must not raise. + cleaned = _scrub_db_overlay_remote_module_loads( + "general_settings", {"pass_through_endpoints": "not-a-list"} + ) + assert cleaned["pass_through_endpoints"] == "not-a-list" + + def test_litellm_jwtauth_custom_validate_stripped(): overlay = { "litellm_jwtauth": {