fix: block yaml and ruamel deserialization in agent-config code references
Merge https://github.com/google/adk-python/pull/6646 Reject yaml and ruamel unsafe/full loaders in config agent to prevent RCE. Note: ruamel is a transitive dev/test dependency, but is blocked to prevent RCE if present in the environment. PiperOrigin-RevId: 967340296
This commit is contained in:
committed by
Copybara-Service
parent
0dbb88c7c4
commit
924d802f5b
@@ -216,6 +216,23 @@ _BLOCKED_MODULES = frozenset({
|
||||
"_testcapi",
|
||||
"_testinternalcapi",
|
||||
"test",
|
||||
# Hard, always-installed third-party dependencies of adk-python itself
|
||||
# (or common transitive dependencies) 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 these are common enough that they are 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) and ruamel.yaml equivalents 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.
|
||||
"ruamel",
|
||||
"yaml",
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -697,6 +697,71 @@ 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 ruamel is a common transitive dependency.
|
||||
# Both ship exec-capable deserialization entry points.
|
||||
_YAML_UNSAFE_LOADER_REFS = [
|
||||
"yaml.unsafe_load",
|
||||
"yaml.load",
|
||||
"yaml.full_load",
|
||||
]
|
||||
|
||||
_RUAMEL_UNSAFE_LOADER_REFS = [
|
||||
"ruamel.yaml.round_trip_load",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"blocked_ref", _YAML_UNSAFE_LOADER_REFS + _RUAMEL_UNSAFE_LOADER_REFS
|
||||
)
|
||||
def test_resolve_code_reference_blocks_yaml_and_ruamel_deserialization(
|
||||
blocked_ref: str,
|
||||
):
|
||||
"""yaml and ruamel's unsafe/full loaders are rejected as code references."""
|
||||
with pytest.raises(ValueError, match="Blocked module reference"):
|
||||
config_agent_utils.resolve_code_reference(CodeConfig(name=blocked_ref))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"blocked_ref", _YAML_UNSAFE_LOADER_REFS + _RUAMEL_UNSAFE_LOADER_REFS
|
||||
)
|
||||
def test_resolve_tools_blocks_yaml_and_ruamel_deserialization(blocked_ref: str):
|
||||
"""yaml and ruamel'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")
|
||||
|
||||
|
||||
_YAML_SAFE_LOOKING_REFS = [
|
||||
"yaml.safe_load",
|
||||
"yaml.dump",
|
||||
"yaml.SafeLoader",
|
||||
]
|
||||
|
||||
_RUAMEL_SAFE_LOOKING_REFS = [
|
||||
"ruamel.yaml.safe_load",
|
||||
"ruamel.yaml.dump",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"blocked_ref", _YAML_SAFE_LOOKING_REFS + _RUAMEL_SAFE_LOOKING_REFS
|
||||
)
|
||||
def test_harmless_looking_yaml_and_ruamel_references_are_also_blocked(
|
||||
blocked_ref: str,
|
||||
):
|
||||
"""The whole yaml and ruamel modules are off-limits, not just the scary parts.
|
||||
|
||||
Blocking them in full locks in the module-wide intent and prevents
|
||||
future bypasses if new unsafe loaders are added or if safe-looking names
|
||||
are refactored to resolve differently.
|
||||
"""
|
||||
with pytest.raises(ValueError, match="Blocked module reference"):
|
||||
config_agent_utils.resolve_code_reference(CodeConfig(name=blocked_ref))
|
||||
|
||||
|
||||
def test_denylist_can_be_disabled():
|
||||
"""Verify _set_enforce_denylist(False) disables module blocking."""
|
||||
config_agent_utils._set_enforce_denylist(False)
|
||||
|
||||
Reference in New Issue
Block a user