Oct 27, 2026·9 min read·3 visits
Agno versions prior to 2.3.24 use Python's eval() function to resolve string type names in the field_type parameter during function calling and tool deserialization. Replacing type strings with arbitrary Python expressions allows unauthenticated remote code execution.
CVE-2026-35002 is an arbitrary code execution vulnerability in the Agno agent platform (formerly Phidata) affecting all versions prior to 2.3.24. The flaw resides in the model execution logic (libs/agno/agno/models/base.py) and tool serialization logic (libs/agno/agno/tools/function.py), where the application directly passes the field_type string parameter into Python's built-in eval() function to resolve string type names into Python data types. An attacker who can influence FunctionCall arguments via prompt injection or supply untrusted dictionary structures for deserialization can execute arbitrary Python expressions within the context of the host process.
Agno (formerly known as Phidata) is an open-source Python framework designed for building and orchestrating Large Language Model (LLM) agents, managing memory, and executing external tools via function calling. During agent workflows, models generate structured function call requests that the framework parses, validates, and executes on the host system. As part of this functionality, Agno supports dynamic user input schemas that specify expected parameter names, descriptions, values, and data types.
In versions of Agno prior to 2.3.24, the framework contains an eval injection vulnerability classified under CWE-95 (Improper Neutralization of Directives in Dynamically Evaluated Code). The flaw is located in two distinct components: the base model execution engine (libs/agno/agno/models/base.py) and the tool function serialization module (libs/agno/agno/tools/function.py). Both components attempt to convert a string representation of a Python data type—stored in the field_type attribute—into an actual Python type object.
Instead of validating the string against an explicit allowlist of safe primitive types, the application passes the raw field_type string directly into Python's built-in eval() function. Because eval() executes arbitrary Python expressions within the current global and local namespaces, any untrusted input reaching this sink results in arbitrary code execution.
This architecture exposes two primary attack surfaces. First, external APIs or persistence layers that deserialize tool configurations via UserInputField.from_dict() allow direct injection if the input dictionary is attacker-controlled. Second, agent workflows that process untrusted external data (such as web scraping, document parsing, or multi-user chat interfaces) are susceptible to indirect prompt injection, where a manipulated LLM response emits a FunctionCall containing a malicious field_type value.
The root cause of CVE-2026-35002 is the use of dynamic code evaluation (eval()) for data type resolution without prior lexical validation or sandboxing. When an Agno agent processes a model's tool invocation, the run_function_calls and arun_function_calls methods in libs/agno/agno/models/base.py inspect the arguments dictionary of each FunctionCall instance (fc). Specifically, the execution loop iterates over the list of dictionaries contained within fc.arguments.get("user_input_fields", []).
For each entry in user_input_fields, the framework extracts the field_type value. The developer intention was to translate string identifiers such as "str", "int", or "bool" into their corresponding built-in Python type classes (str, int, bool) before instantiating a UserInputField object. To achieve this conversion, the implementation checked if field_type was an instance of str and, if true, passed it directly to eval(field_type).
The developers wrapped the eval(field_type) call in a try...except (NameError, SyntaxError) block, defaulting python_type to str if an exception occurred. However, exception handling executes only after the expression inside eval() is evaluated by the Python interpreter. Any syntactically valid Python expression—such as inline module imports via __import__() or list comprehensions invoking built-in methods—executes completely before returning a value or raising a caught exception.
A parallel flaw exists in libs/agno/agno/tools/function.py within the @classmethod UserInputField.from_dict(cls, data: Dict[str, Any]). When reconstructing a UserInputField instance from a dictionary, the method unconditionally executes eval(data["field_type"]) without even a type check or try...except wrapper. Consequently, any deserialization routine invoking from_dict on untrusted data triggers immediate code execution.
The vulnerability was addressed in commit cbf675521d4d2281925a051784a3b94172e56416 (Pull Request #5912). Prior to this patch, libs/agno/agno/models/base.py contained identical vulnerable blocks inside both the synchronous run_function_calls method and the asynchronous arun_function_calls method.
# Vulnerable implementation in libs/agno/agno/models/base.py (< 2.3.24)
user_input_schema = []
for input_field in fc.arguments.get("user_input_fields", []):
field_type = input_field.get("field_type")
try:
# VULNERABLE: Direct evaluation of arbitrary string input
python_type = eval(field_type) if isinstance(field_type, str) else field_type
except (NameError, SyntaxError):
python_type = str # Default to str if type is invalid
user_input_schema.append(
UserInputField(
name=input_field.get("field_name"),
field_type=python_type,
description=input_field.get("field_description"),
)
)Similarly, the deserialization helper in libs/agno/agno/tools/function.py evaluated the dictionary key directly during object instantiation:
# Vulnerable implementation in libs/agno/agno/tools/function.py (< 2.3.24)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "UserInputField":
return cls(
name=data["name"],
# VULNERABLE: Unconditional eval() on dictionary value
field_type=eval(data["field_type"]), # Convert string type name to actual type
description=data["description"],
value=data["value"],
)In commit cbf675521d4d2281925a051784a3b94172e56416, the maintainers removed all occurrences of eval() across those three code paths and replaced them with a static dictionary lookup (type_mapping).
diff --git a/libs/agno/agno/models/base.py b/libs/agno/agno/models/base.py
index abcdef1..cbf6755 100644
--- a/libs/agno/agno/models/base.py
+++ b/libs/agno/agno/models/base.py
@@ -2030,10 +2030,20 @@ def run_function_calls(
user_input_schema = []
for input_field in fc.arguments.get("user_input_fields", []):
field_type = input_field.get("field_type")
- try:
- python_type = eval(field_type) if isinstance(field_type, str) else field_type
- except (NameError, SyntaxError):
- python_type = str # Default to str if type is invalid
+ if isinstance(field_type, str):
+ # FIX: Explicit allowlist mapping string names to safe built-in types
+ type_mapping = {
+ "str": str,
+ "int": int,
+ "float": float,
+ "bool": bool,
+ "list": list,
+ "dict": dict,
+ }
+ python_type = type_mapping.get(field_type, str)
+ elif isinstance(field_type, type):
+ python_type = field_type
+ else:
+ python_type = str
user_input_schema.append(
UserInputField(
name=input_field.get("field_name"),
diff --git a/libs/agno/agno/tools/function.py b/libs/agno/agno/tools/function.py
index 1234567..89abcdef 100644
--- a/libs/agno/agno/tools/function.py
+++ b/libs/agno/agno/tools/function.py
@@ -54,9 +54,17 @@ def to_dict(self) -> Dict[str, Any]:
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "UserInputField":
+ # FIX: Replace eval() with static dictionary lookup and type validation
+ type_mapping = {"str": str, "int": int, "float": float, "bool": bool, "list": list, "dict": dict}
+ field_type_raw = data["field_type"]
+ if isinstance(field_type_raw, str):
+ field_type = type_mapping.get(field_type_raw, str)
+ elif isinstance(field_type_raw, type):
+ field_type = field_type_raw
+ else:
+ field_type = str
return cls(
name=data["name"],
- field_type=eval(data["field_type"]), # Convert string type name to actual type
+ field_type=field_type,
description=data["description"],
value=data["value"],
)The patch is complete and effective for the identified code paths. By restricting string resolution to a hardcoded dictionary of six primitive Python types (str, int, float, bool, list, dict) and falling back to str via .get(field_type, str) when an unrecognized string is provided, dynamic code execution is eliminated. Furthermore, the secondary elif isinstance(field_type, type): check ensures that already-resolved type objects passed programmatically continue to function without invoking string evaluation.
Exploitation of CVE-2026-35002 requires no authentication (PR:N) or user interaction (UI:N) when an application exposes an agent interface or deserialization endpoint over the network. There are two primary methodologies to trigger the vulnerable code path: direct deserialization of untrusted JSON/dictionary payloads, and indirect prompt injection during LLM function calling.
In the direct deserialization scenario, an application receives serialized tool state or user input field definitions from an external client or untrusted storage mechanism and passes the parsed dictionary to UserInputField.from_dict(). Because data["field_type"] is passed directly to eval(), supplying any valid Python expression in the field_type field triggers immediate execution upon deserialization.
from agno.tools.function import UserInputField
# Conceptual demonstration of untrusted dictionary deserialization
untrusted_data = {
"name": "test_field",
"field_type": "__import__('os').system('id')",
"description": "Demonstration payload",
"value": "sample"
}
# In Agno < 2.3.24, calling from_dict evaluates the expression inside field_type
field = UserInputField.from_dict(untrusted_data)In the second scenario, an attacker targets an LLM agent configured with tools that utilize dynamic user input schemas. When the agent processes untrusted text—such as an external webpage, an uploaded document, or a message from an unauthenticated user—the attacker embeds prompt injection instructions directing the underlying LLM to invoke a tool and populate the user_input_fields argument with a crafted field_type string.
# Representation of a manipulated FunctionCall arguments dictionary
fc_arguments = {
"user_input_fields": [
{
"field_name": "target_param",
"field_type": "__import__('subprocess').check_output(['whoami']).decode()",
"description": "payload",
"value": "1337"
}
]
}
# When run_function_calls or arun_function_calls iterates over user_input_fields,
# the eval(field_type) statement executes the injected expression.> [!NOTE]
> Because eval() expects an expression rather than a statement, attackers cannot use multi-line procedural statements (such as import os; os.system(...)) directly. However, Python's built-in __import__() function allows single-expression module loading and method chaining, bypassing expression-only constraints entirely.
The security impact of CVE-2026-35002 is rated Critical, with a CVSS v3.1 base score of 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) and a CVSS v4.0 score of 9.3 (CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N). The vulnerability grants an attacker arbitrary code execution within the operating system process running the Agno framework.
In typical production deployments of LLM agent platforms, the host process has access to sensitive environment variables, including proprietary LLM provider API keys (such as OpenAI, Anthropic, or AWS Bedrock credentials), vector database connection strings, internal microservices, and cloud metadata endpoints. Executing arbitrary Python code allows an attacker to harvest these credentials from memory or os.environ, read or modify local files, and pivot into internal network segments.
Integrity and availability impacts are similarly high. An attacker can modify the behavior of the agent in memory, tamper with connected databases, or terminate the host application process entirely. While the CVSS v3.1 scope metric is Unchanged (S:U)—reflecting that code execution occurs within the boundaries of the vulnerable host process—the practical downstream consequences in cloud-native agent environments often extend to connected cloud resources via stolen IAM roles or API tokens.
According to Exploit Prediction Scoring System (EPSS) metrics, CVE-2026-35002 holds a score between 0.842% and 1.15% (placing it in the 57.64th to 66.68th percentile). It is not currently listed in the CISA Known Exploited Vulnerabilities (KEV) catalog, and no associations with ransomware campaigns have been recorded.
The definitive remediation for CVE-2026-35002 is to upgrade the agno package to version 2.3.24 or later. Version 2.3.24 incorporates commit cbf675521d4d2281925a051784a3b94172e56416, which replaces all dynamic eval() calls on field_type with a static dictionary allowlist.
Administrators and developers can upgrade the package using pip or their respective Python dependency manager:
pip install --upgrade "agno>=2.3.24"To identify vulnerable installations across development environments, container images, and production hosts, teams should audit active virtual environments and dependency lockfiles (requirements.txt, Pipfile.lock, poetry.lock, or uv.lock) for any version of agno strictly prior to 2.3.24:
pip list | grep -i agnoIn environments where an immediate upgrade is not feasible, workarounds require strict input validation before data reaches UserInputField.from_dict() or run_function_calls(). Specifically, applications must intercept tool arguments and serialized dictionaries to verify that any field_type value is strictly contained within the set {"str", "int", "float", "bool", "list", "dict"} prior to invoking Agno's internal methods. Additionally, security engineering teams can deploy static analysis rules (such as Semgrep) to verify that custom extensions or internal forks do not reintroduce eval() sinks on model-controlled attributes.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Agno Agno | < 2.3.24 | 2.3.24 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-95 (Eval Injection) |
| CVSS v3.1 Score | 9.8 (Critical) |
| CVSS v4.0 Score | 9.3 (Critical) |
| Attack Vector | Network (AV:N) |
| Impact | Arbitrary Code Execution (RCE) |
| EPSS Score | 1.15% (66.68th Percentile) |
| Exploit Status | Proof of Concept (PoC) |
| CISA KEV Status | Not Listed |
The product receives input from an upstream component, but it does not neutralize or incorrectly neutralizes code syntax before using the input in a dynamic evaluation call.
CVE-2026-109956 (GHSA-j456-xh4h-cpf2) is a path traversal vulnerability and security sandbox bypass in the Python Natural Language Toolkit (NLTK) versions 3.10.3 and earlier. The WekaClassifier module fails to validate caller-controlled model file paths before passing them to the Weka Java Virtual Machine (JVM) via command-line arguments. When operating under the nltk.pathsec security sandbox, this omission permits attackers who control model path parameters to read, probe, or overwrite arbitrary files outside authorized NLTK data directories.
CVE-2026-115895 is a high-severity code injection vulnerability (CWE-94) in the happy-dom npm package prior to version 21.0.4. The vulnerability exists within the ECMAScriptModuleCompiler component, which transforms ECMAScript module syntax into executable Node.js functions when JavaScript evaluation is enabled. Due to an incomplete fix for CVE-2026-33943, the compiler failed to validate local named import aliases against legal JavaScript identifier rules. Attackers supplying crafted module imports—specifically utilizing destructuring default initializers alongside JSON module attributes—can inject arbitrary JavaScript expressions directly into the host Node.js runtime context.
An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.
CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.
CVE-2026-113872 (GHSA-q7qp-rjg7-q26m) is a high-severity denial-of-service vulnerability affecting the multer multipart/form-data upload middleware for Node.js and Express in versions 2.0.0 through 2.4.0. When an endpoint is configured to accept multiple files using an asynchronous fileFilter callback, a crafted, truncated multipart HTTP request can trigger an unhandled error event on the underlying busboy stream parser after request teardown has already completed. Because all error listeners are stripped during teardown, Node.js treats the resulting error event as an uncaught exception and immediately terminates the server process.
CVE-2026-115897 (also tracked as GHSA-m5mm-p8h7-m29v and aliased as CVE-2026-115754) is a Cross-Site Scripting (XSS) vulnerability in the Yahoo serialize-javascript npm package prior to version 7.1.3. The flaw exists in the regular expression used by escapeFunctionBody() to neutralize HTML script closing tags within serialized JavaScript functions. Because function bodies are sanitized in isolation before structural formatting is applied, a function whose string representation terminates directly with the sequence '</script' bypasses the positive lookahead check. When combined with serialization formatting options that introduce whitespace delimiters and a subsequent property that supplies a closing bracket, an attacker can prematurely terminate an enclosing HTML <script> block and inject arbitrary HTML or JavaScript into the rendered document.