From 259cccd6c05f344d7a6df904beb0964f1a9988b3 Mon Sep 17 00:00:00 2001 From: Omry Yadan Date: Fri, 28 Aug 2026 05:31:38 +0800 Subject: [PATCH] Harden deferred instantiate dispatch on 1.3 Reject the two-argument iter callback overload while preserving ordinary iteration, and deny direct partial-call and property-getter dispatch before side effects can run. Complements the coordinated #3412/#3413 policy backport. --- hydra/_internal/instantiate/_instantiate2.py | 13 +++++ tests/instantiate/test_instantiate.py | 61 +++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/hydra/_internal/instantiate/_instantiate2.py b/hydra/_internal/instantiate/_instantiate2.py index 3e447b61c7..2d03567fa2 100644 --- a/hydra/_internal/instantiate/_instantiate2.py +++ b/hydra/_internal/instantiate/_instantiate2.py @@ -69,6 +69,7 @@ "concurrent.futures.process.ProcessPoolExecutor.map", "concurrent.futures.process.ProcessPoolExecutor.submit", "concurrent.futures.thread.ThreadPoolExecutor.submit", + "functools.partial.__call__", "functools.reduce", "itertools.accumulate", "itertools.groupby", @@ -86,6 +87,7 @@ } _CALLABLE_DESCRIPTOR_BINDING_TARGETS: Dict[type, str] = { + property: "builtins.property.__get__", types.ClassMethodDescriptorType: "types.ClassMethodDescriptorType.__get__", types.FunctionType: "types.FunctionType.__get__", types.MethodDescriptorType: "types.MethodDescriptorType.__get__", @@ -484,6 +486,17 @@ def _authorize_target_invocation( allow_incomplete_partial: bool = False, ) -> None: target_name = _get_resolved_target_name_for_check(target) + if target_name == "builtins.iter" and len(args) == 2: + msg = dedent( + """\ + Target 'builtins.iter' cannot use its two-argument callback form from + config because callback execution is deferred beyond instantiate's + target authorization. Use one-argument iter(iterable), or perform the + callback iteration in trusted Python code. This restriction cannot be + bypassed with HYDRA_INSTANTIATE_ALLOWLIST_OVERRIDE.""" + ) + raise InstantiationException(_with_full_key(msg, full_key)) + if target_name in _NON_CALLABLE_MOCK_TARGETS: unsafe_parameters = sorted( set(kwargs).difference(_NON_CALLABLE_MOCK_SAFE_PARAMETERS) diff --git a/tests/instantiate/test_instantiate.py b/tests/instantiate/test_instantiate.py index d0c60ccf97..9b1b981ff5 100644 --- a/tests/instantiate/test_instantiate.py +++ b/tests/instantiate/test_instantiate.py @@ -1881,7 +1881,6 @@ def test_resolved_target_aliases_are_blocklisted( "target", [ "builtins.filter", - "builtins.iter", "itertools.dropwhile", "itertools.filterfalse", "itertools.takewhile", @@ -1891,6 +1890,66 @@ def test_non_result_lazy_callback_targets_are_not_blocklisted(target: str) -> No assert not _instantiate2._is_blocklisted_target(target) +def test_one_argument_iter_target_is_allowed() -> None: + result = _instantiate2.instantiate( + {"_target_": "builtins.iter", "_args_": [[1, 2]]} + ) + + assert list(result) == [1, 2] + + +def test_two_argument_iter_callback_cannot_be_allowlisted(monkeypatch: Any) -> None: + calls: List[str] = [] + + def callback() -> int: + calls.append("called") + return 1 + + monkeypatch.setenv("HYDRA_INSTANTIATE_ALLOWLIST_OVERRIDE", "builtins.iter") + cfg = {"_target_": "builtins.iter", "_args_": [callback, 2]} + with raises(InstantiationException, match="two-argument callback form"): + _instantiate2.instantiate(cfg) + + assert calls == [] + + +def test_partial_call_target_cannot_be_allowlisted(monkeypatch: Any) -> None: + calls: List[str] = [] + + def callback() -> None: + calls.append("called") + + target = "functools.partial.__call__" + monkeypatch.setenv("HYDRA_INSTANTIATE_ALLOWLIST_OVERRIDE", target) + cfg = {"_target_": target, "_args_": [partial(callback)]} + with raises(InstantiationException, match="cannot be authorized"): + _instantiate2.instantiate(cfg) + + assert calls == [] + + +def test_property_get_target_cannot_be_allowlisted(monkeypatch: Any) -> None: + calls: List[str] = [] + + class Receiver: + pass + + def getter(_: Receiver) -> int: + calls.append("called") + return 42 + + target = "builtins.property.__get__" + monkeypatch.setenv("HYDRA_INSTANTIATE_ALLOWLIST_OVERRIDE", target) + cfg = { + "_target_": target, + "_args_": [property(getter), Receiver(), Receiver], + } + with raises(InstantiationException, match="cannot be authorized"): + _instantiate2.instantiate(cfg) + + assert calls == [] + + def test_discovery_target_applies_blocklist_to_selected_path() -> None: cfg = {"_target_": "hydra.utils.get_method", "path": "builtins.eval"} with raises(InstantiationException, match="Target 'builtins.eval'.*blocklisted"):