From 23dc40076771b730a2fee0215d36633cbc2dc5c4 Mon Sep 17 00:00:00 2001 From: AsroyxCySec Date: Wed, 5 Aug 2026 18:32:30 +0700 Subject: [PATCH] agents: block all stdlib modules in agent-config code-refs The agent-config denylist (`_BLOCKED_MODULES`) only compared the top-level module name of a code-ref against a hand-maintained list of dangerous stdlib modules. That list is inherently incomplete: equivalent code-execution gadgets slip through. For example `profile` is blocked (its `profile.run("")` runs arbitrary code) but its C sibling `cProfile` is not, and `cProfile.run("")` is the identical gadget. `timeit.timeit`, `pydoc`, `logging.config.fileConfig`, `bdb`, `trace` and `venv` are similarly reachable. Agent-config tool/callback/model/schema references point to user-defined or ADK packages, never to the standard library, so reject any stdlib top-level module via `sys.stdlib_module_names` (Python 3.10+, matching requires-python). This closes the bypass class instead of chasing individual gadgets. `_BLOCKED_MODULES` is kept for documentation/defense-in-depth; legitimate references (user packages, `google.adk.*`) are unaffected. Adds regression tests for the previously-bypassing gadgets and a test that user/ADK references still validate. --- src/google/adk/agents/config_agent_utils.py | 12 +++++- tests/unittests/agents/test_agent_config.py | 43 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/google/adk/agents/config_agent_utils.py b/src/google/adk/agents/config_agent_utils.py index 72648faa26d..c44bd8fc77c 100644 --- a/src/google/adk/agents/config_agent_utils.py +++ b/src/google/adk/agents/config_agent_utils.py @@ -17,6 +17,7 @@ import importlib import inspect import os +import sys from typing import Any from typing import List @@ -204,7 +205,16 @@ def _validate_module_reference(fully_qualified_name: str) -> None: return # Extract the top-level package from the fully-qualified name. top_module = fully_qualified_name.split(".")[0] - if top_module in _BLOCKED_MODULES: + # Agent-config tool/callback/model/schema references point to user-defined or + # ADK packages, never to the Python standard library. A hand-maintained + # denylist of dangerous stdlib modules is inherently incomplete -- equivalent + # code-execution gadgets slip through (for example ``cProfile.run`` vs. the + # already-blocked ``profile.run``, as well as ``timeit.timeit``, ``pydoc``, + # ``logging.config``, ``bdb`` and ``trace``). Blocking the entire standard + # library by name closes that class of bypass, while legitimate references + # (user packages, ``google.adk.*``) are unaffected because they are not part + # of ``sys.stdlib_module_names``. + if top_module in _BLOCKED_MODULES or top_module in sys.stdlib_module_names: raise ValueError( f"Blocked module reference: {fully_qualified_name!r}. " f"Importing from the '{top_module}' module is not allowed in " diff --git a/tests/unittests/agents/test_agent_config.py b/tests/unittests/agents/test_agent_config.py index 78b25ee905e..abfb84d27ee 100644 --- a/tests/unittests/agents/test_agent_config.py +++ b/tests/unittests/agents/test_agent_config.py @@ -604,6 +604,49 @@ def test_newly_blocked_network_modules_are_rejected(blocked_ref: str): assert "Blocked module reference" in str(exc_info.value.__cause__) +@pytest.mark.parametrize( + "blocked_ref", + [ + # Code-execution gadgets that a top-level-name denylist previously + # missed. ``cProfile.run`` mirrors the already-blocked ``profile.run``; + # the others reach code execution directly or via os/subprocess. + # Blocking the whole standard library closes this bypass class. + "cProfile.run", + "timeit.timeit", + "pydoc.render_doc", + "logging.config.fileConfig", + "bdb.Bdb", + "trace.Trace", + "venv.create", + ], +) +def test_stdlib_gadget_modules_are_rejected(blocked_ref: str): + """Non-denylisted stdlib modules that can execute code must also be blocked. + + Regression test for the denylist bypass: a top-level-name denylist inherently + misses equivalent gadgets (e.g. ``cProfile.run`` vs. the blocked + ``profile.run``). All standard-library modules are now rejected. + """ + with pytest.raises( + ValueError, match="Invalid fully qualified name" + ) as exc_info: + config_agent_utils.resolve_fully_qualified_name(blocked_ref) + assert "Blocked module reference" in str(exc_info.value.__cause__) + + +def test_non_stdlib_references_are_not_blocked(): + """Legitimate references (user/ADK packages) must not be blocked. + + The standard-library rule must only reject stdlib top-level modules, never + user-defined packages or ``google.adk.*`` references. ``_validate_module_ + reference`` only inspects the name (no import), so it must not raise here. + """ + config_agent_utils._validate_module_reference("my_company_pkg.my_tool") + config_agent_utils._validate_module_reference( + "google.adk.tools.google_search" + ) + + def test_denylist_can_be_disabled(): """Verify _set_enforce_denylist(False) disables module blocking.""" config_agent_utils._set_enforce_denylist(False)