Skip to content
Open
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
12 changes: 11 additions & 1 deletion src/google/adk/agents/config_agent_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import importlib
import inspect
import os
import sys
from typing import Any
from typing import List

Expand Down Expand Up @@ -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 "
Expand Down
43 changes: 43 additions & 0 deletions tests/unittests/agents/test_agent_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading