VAITP Dataset

← Back to the dataset

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 ::