Tool Use and Function Calling in LLMs
Think of getattr as a universal key that can unlock any door in a house, provided you know the door's name. When an LLM provides the key name, you're handing it the power to open any door it desires. A tool registry, conversely, is like a bouncer at the door, only allowing access to a pre-approved, safe list of specific rooms.
getattr or eval; always enforce execution through a strict, hard-coded tool registry map.The Setup
You are building an agent that can interact with system utilities. You define a class containing safe operations and write an executor that looks up tool names returned by the LLM and dynamically invokes them using arguments parsed from JSON.
What Does This Print?
import subprocess
class SystemTools:
def get_disk_space(self) -> str:
return "Disk space is optimal."
def run_diagnostics(self) -> str:
return "All systems normal."
def execute_llm_tool(tool_name: str, arguments: dict) -> str:
# Dynamic lookup to matching method using string from LLM
tools = SystemTools()
method = getattr(tools, tool_name)
return method(**arguments)
# A malicious agent prompt injection instructs the LLM to call a private system method
compromised_tool = "__init__"
execute_llm_tool(compromised_tool, {})
The Output
The dynamic lookup on __init__ succeeded silently. Python's object.__init__() was called on the already-instantiated object with no extra arguments and returned None — no exception, no crash. The executor returned None where a str was expected, and the application never noticed. More critically, the attack surface extends to every dunder in the MRO: __reduce__ enables pickle-based code injection, __subclasses__() (via __class__) can walk the entire live object graph, and __getattribute__ overrides attribute resolution entirely.
Why Python Does This
In Python, classes, functions, and objects are completely introspectable through attributes. The getattr(object, name) built-in resolves name lookups dynamically by looking up the string in object.__dict__ and ascending the class inheritance tree (defined in __mro__). Python does not have strict private scopes; all methods—including constructors, properties, and built-in dunders—are accessible to dynamic lookups. If you pass raw LLM string predictions directly into getattr without filtering against an explicit whitelist of registered safe tools, you expose the entire runtime scope of that object to the LLM. Attackers using prompt injection can manipulate the tool payload to target private Python operations, invoking system capabilities outside of your designed scope.
The Fix
class SystemTools:
def get_disk_space(self) -> str:
return "Disk space is optimal."
def run_diagnostics(self) -> str:
return "All systems normal."
# Explicit, secure tool registry mapping names to bounded methods
SAFE_TOOL_REGISTRY = {
"get_disk_space": SystemTools().get_disk_space,
"run_diagnostics": SystemTools().run_diagnostics
}
def execute_llm_tool_safe(tool_name: str, arguments: dict) -> str:
if tool_name not in SAFE_TOOL_REGISTRY:
raise ValueError(f"Unauthorized tool execution request: {tool_name}")
# Only invoke validated and explicitly registered tools
method = SAFE_TOOL_REGISTRY[tool_name]
return method(**arguments)
A hard-coded tool registry maps safe, public-facing tool names (e.g., "get_disk_space") to their actual Python callable functions. This acts as a whitelist, ensuring that only explicitly allowed functions can be invoked, irrespective of what the LLM might hallucinate or be prompted to request.
How This Fails in Real Systems
A DevOps support bot processed alerts. An injection attack in a target alert string forced the LLM to trigger a private framework utility method via getattr. This allowed the attacker to list current server environment variables, leaking production API keys and database credentials to the open web.
Key Takeaway
getattr or eval; always enforce execution through a strict, hard-coded tool registry map.getattr with untrusted input, failing to realize it can dynamically access any attribute or method on an object, including private ones, leading to security vulnerabilities or unexpected behavior.