Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
  • Loading branch information
omry committed Aug 27, 2026
commit 259cccd6c05f344d7a6df904beb0964f1a9988b3
13 changes: 13 additions & 0 deletions hydra/_internal/instantiate/_instantiate2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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__",
Expand Down Expand Up @@ -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)
Expand Down
61 changes: 60 additions & 1 deletion tests/instantiate/test_instantiate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1881,7 +1881,6 @@ def test_resolved_target_aliases_are_blocklisted(
"target",
[
"builtins.filter",
"builtins.iter",
"itertools.dropwhile",
"itertools.filterfalse",
"itertools.takewhile",
Expand All @@ -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"):
Expand Down