diff --git a/src/google/adk/agents/config_agent_utils.py b/src/google/adk/agents/config_agent_utils.py index d0046edd5b..5220456058 100644 --- a/src/google/adk/agents/config_agent_utils.py +++ b/src/google/adk/agents/config_agent_utils.py @@ -216,6 +216,22 @@ def _load_config_from_path(config_path: str) -> AgentConfig: "_testcapi", "_testinternalcapi", "test", + # Hard, always-installed third-party dependencies of adk-python itself + # that ship exec-capable deserialization entry points. A denylist still + # cannot cover third-party packages in general (the loader resolves them + # by name, and any of the many packages an integration might install + # could have its own gadget), but this one ships with every adk-python + # install regardless of which integrations are used, so it is blocked + # outright rather than left to the general third-party gap. + # + # yaml.unsafe_load (and yaml.load without an explicit safe Loader, and + # yaml.full_load) construct arbitrary Python objects from the YAML + # document they are given, via tags such as + # !!python/object/apply:os.system. A reference to any of these as a + # code-reference field's target -- with the YAML content supplied as + # the function's argument at call time -- is a direct RCE primitive + # requiring no other preconditions. + "yaml", }) diff --git a/tests/unittests/agents/test_agent_config.py b/tests/unittests/agents/test_agent_config.py index cdc4c92422..c1d0ec8c57 100644 --- a/tests/unittests/agents/test_agent_config.py +++ b/tests/unittests/agents/test_agent_config.py @@ -697,6 +697,45 @@ def test_third_party_module_reference_is_not_blocked(): assert result is BaseModel +# yaml is a hard, always-installed dependency of adk-python itself (not an +# optional integration), and ships exec-capable deserialization entry points +# that take a single string argument and need no other preconditions. Unlike +# third-party packages in general, this one is present in every install, so +# it is blocked outright rather than left to the general third-party gap +# _validate_module_reference cannot close. +_YAML_RCE_REFS = [ + "yaml.unsafe_load", + "yaml.load", + "yaml.full_load", +] + + +@pytest.mark.parametrize("blocked_ref", _YAML_RCE_REFS) +def test_resolve_code_reference_blocks_yaml_deserialization(blocked_ref: str): + """yaml's unsafe/full loaders are rejected as code references. + + This is the path a prior reported exploit took for a stdlib module + (cProfile.run, see test_resolve_tools_blocks_exec_capable_stdlib above): + upload an agent YAML whose only tool is the dangerous reference, then + dispatch a functionCall to it with a malicious YAML string as the + argument. yaml.unsafe_load is the equivalent primitive for a hard, + always-installed third-party dependency rather than the standard + library. + """ + with pytest.raises(ValueError, match="Blocked module reference"): + config_agent_utils.resolve_code_reference(CodeConfig(name=blocked_ref)) + + +@pytest.mark.parametrize("blocked_ref", _YAML_RCE_REFS) +def test_resolve_tools_blocks_yaml_deserialization(blocked_ref: str): + """yaml's unsafe/full loaders are rejected as user-defined tools.""" + from google.adk.tools.tool_configs import ToolConfig + + tool_config = ToolConfig(name=blocked_ref) + with pytest.raises(ValueError, match="Blocked module reference"): + LlmAgent._resolve_tools([tool_config], "/fake/path.yaml") + + def test_denylist_can_be_disabled(): """Verify _set_enforce_denylist(False) disables module blocking.""" config_agent_utils._set_enforce_denylist(False)