From 7a1a09ebe0a765b8d2cf5ea874c0faeb6883ac5a Mon Sep 17 00:00:00 2001 From: Dean Schmigelski Date: Wed, 19 Nov 2025 17:02:48 -0500 Subject: [PATCH] security(tool_loader): prevent tool name and sys modules collisions in tool_loader --- src/strands/tools/loader.py | 6 ++++-- tests/strands/tools/test_loader.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/strands/tools/loader.py b/src/strands/tools/loader.py index 31e8dc788..6f745b728 100644 --- a/src/strands/tools/loader.py +++ b/src/strands/tools/loader.py @@ -17,6 +17,8 @@ logger = logging.getLogger(__name__) +_TOOL_MODULE_PREFIX = "_strands_tool_" + def load_tool_from_string(tool_string: str) -> List[AgentTool]: """Load tools follows strands supported input string formats. @@ -65,7 +67,7 @@ def load_tools_from_file_path(tool_path: str) -> List[AgentTool]: module = importlib.util.module_from_spec(spec) # Load, or re-load, the module - sys.modules[module_name] = module + sys.modules[f"{_TOOL_MODULE_PREFIX}{module_name}"] = module # Execute the module to run any top level code spec.loader.exec_module(module) @@ -200,7 +202,7 @@ def load_python_tools(tool_path: str, tool_name: str) -> List[AgentTool]: raise ImportError(f"No loader available for {tool_name}") module = importlib.util.module_from_spec(spec) - sys.modules[tool_name] = module + sys.modules[f"{_TOOL_MODULE_PREFIX}{tool_name}"] = module spec.loader.exec_module(module) # Collect function-based tools decorated with @tool diff --git a/tests/strands/tools/test_loader.py b/tests/strands/tools/test_loader.py index 13aca90c3..1c665b42a 100644 --- a/tests/strands/tools/test_loader.py +++ b/tests/strands/tools/test_loader.py @@ -1,12 +1,13 @@ import os import re +import sys import tempfile import textwrap import pytest from strands.tools.decorator import DecoratedFunctionTool -from strands.tools.loader import ToolLoader, load_tools_from_file_path +from strands.tools.loader import _TOOL_MODULE_PREFIX, ToolLoader, load_tools_from_file_path from strands.tools.tools import PythonAgentTool @@ -317,3 +318,29 @@ def test_load_tools_from_file_path_module_spec_missing(): with tempfile.NamedTemporaryFile() as f: with pytest.raises(ImportError, match=f"Could not create spec for {os.path.basename(f.name)}"): load_tools_from_file_path(f.name) + + +def test_tool_module_prefix_prevents_collision(): + """Test that tool modules are loaded with prefix to prevent sys.modules collisions.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write( + textwrap.dedent(""" + import strands + + @strands.tools.tool + def test_tool(): + return "test" + """) + ) + f.flush() + + # Load the tool + tools = load_tools_from_file_path(f.name) + + # Check that module is in sys.modules with prefix + module_name = os.path.basename(f.name).split(".")[0] + prefixed_name = f"{_TOOL_MODULE_PREFIX}{module_name}" + + assert prefixed_name in sys.modules + assert len(tools) == 1 + assert tools[0].tool_name == "test_tool"