CVE-2026-61536
Banks: Unsanitized import_path in tool calls leads to code execution.
- CVSS 7.5
- 94
- Input Validation and Sanitization
- Remote
Banks generates meaningful LLM prompts using a simple template language. In versions prior to 2.4.3, banks parses Tool JSON objects from the rendered body of {% completion %} blocks and later resolves their import_path field through importlib.import_module(…) + getattr(…) to obtain the callable that handles a tool call. There is no allowlist or sanitization on import_path, so any importable Python attribute (e.g. os.system, subprocess.getoutput) can be selected. When the LLM emits a tool_calls entry whose function.name matches the attacker-supplied tool name, the resolved callable is invoked with kwargs decoded from tool_call.function.arguments, yielding arbitrary code execution in the banks-hosting process. This is distinct from GHSA-gphh-9q3h-jgpp / CVE-2026-44209. That advisory was fixed in 2.4.2 by switching src/banks/env.py from Environment to SandboxedEnvironment. The fix does not touch src/banks/extensions/completion.py, and the unsafe import + getattr chain still executes on 2.4.2. The malicious Tool JSON is plain text in the rendered template body — it requires no Jinja attribute access, so the sandbox is irrelevant. This issue has been fixed in version 2.4.3.
- CWE
- 94
- CVSS base score
- 7.5
- Published
- 2026-07-30
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Insecure Parsing or Deserialization
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Banks
- Fixed by upgrading
- Yes
Solution
Upgrade Banks to version 2.4.3 or later.
Vulnerable code sample
import importlib
import json
class ToolExecutor:
def __init__(self):
self.tools = {}
def register_tool(self, tool_definition: dict):
self.tools[tool_definition["name"]] = tool_definition
def execute_tool_call(self, tool_call: dict):
tool_name = tool_call["function"]["name"]
tool_definition = self.tools.get(tool_name)
if not tool_definition:
raise ValueError(f"Tool '{tool_name}' is not defined.")
import_path = tool_definition["import_path"]
kwargs = json.loads(tool_call["function"]["arguments"])
# VULNERABLE: import_path comes from the rendered template body and is resolved unchecked
module_path, attribute_name = import_path.rsplit(".", 1)
module = importlib.import_module(module_path)
callable_func = getattr(module, attribute_name)
return callable_func(**kwargs)Patched code sample
import importlib
import json
ALLOWED_TOOL_PATHS = {
"my_safe_tools.get_weather_data",
"my_safe_tools.send_notification",
}
class ToolExecutor:
def __init__(self):
self.tools = {}
def register_tool(self, tool_definition: dict):
self.tools[tool_definition["name"]] = tool_definition
def execute_tool_call(self, tool_call: dict):
tool_name = tool_call["function"]["name"]
tool_definition = self.tools.get(tool_name)
if not tool_definition:
raise ValueError(f"Tool '{tool_name}' is not defined.")
import_path = tool_definition["import_path"]
kwargs = json.loads(tool_call["function"]["arguments"])
# FIX: only resolve import_path values present in the allowlist
if import_path not in ALLOWED_TOOL_PATHS:
raise ValueError(f"Tool path '{import_path}' is not allowed.")
module_path, attribute_name = import_path.rsplit(".", 1)
module = importlib.import_module(module_path)
callable_func = getattr(module, attribute_name)
return callable_func(**kwargs)Payload
{% completion %}
[
{
"type": "function",
"function": {
"name": "run_shell_command",
"description": "Executes a shell command.",
"import_path": "os.system",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The shell command to execute."
}
},
"required": ["command"]
}
}
}
]
{% endcompletion %}
Cite this entry
@misc{vaitp:cve202661536,
title = {{Banks: Unsanitized import_path in tool calls leads to code execution.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-61536},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-61536/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
