Our team has identified a remote code execution (RCE) achieved through a combination of prompt injection and command execution vulnerabilities in one of the langchain-experimental modules — PALChain. This module is part of a system leveraging Program-Aided Language Models (PAL) to generate and execute Python code in response to user queries.
This implementation is particularly susceptible to security risks due to its reliance on dynamic code generation and execution.
After Palo Alto identified the initial flaw CVE-2023-44467, the vendor publicly acknowledged the security risks associated with this component and introduced additional guardrails for code execution. However, our team successfully demonstrated a bypass of these protections.
The effectiveness of the prompt injection aspect of the attack is highly dependent on the specific LLM used. For stability and to focus on the bypass mechanism, we employed a standard OpenAI client.
Evaluation of the protections
To mitigate risks, PALChain incorporates several practical security measures and employs Abstract Syntax Tree (AST) analysis. You can find more details in `langchain-experimental/libs/experimental/langchain_experimental/pal_chain/base.py`.
Introduction to AST Analysis
Abstract Syntax Tree (AST) Analysis is a technique used to parse and analyze the structure of Python code. It transforms source code into a tree representation where nodes represent programming constructs like loops, variables, and functions. AST is widely used in applications like linters, code analysis tools, and compilers because it allows programmatic inspection and modification of code.
Example: Let's translate python instruction code = "x = 5"
into the abstract syntax tree:
Abstract Syntax Tree:
Security Measures in Python Code with AST Analysis
1. Validating Code Syntax with AST
The ast.parse
function converts Python code into AST structure ensuring it’s syntactically valid. If any syntax error or invalid token is encountered, ast.parse
raises an exception (e.g. SyntaxError
), blocking further execution of malformed code.
Code Snippet:
2. Blocking unsafe functions
The ast.walk
function traverses the entire AST, all function calls are inspected for dangerous commands such as eval, exec, system and others. These commands are flagged using AST nodes. e.g os.system("ls")
.
Code Snippet:
3. Restricting Import Statements
Nodes of type ast.Import
or ast.ImportFrom
represent import statements. If allow_imports
is False, any such statement leads to a validation failure. e.g import subprocess; subprocess.run(["ls"])
.
Code Snippet:
4. Restricting Access to Unsafe Attributes
The ast.Attribute
nodes are inspected for dangerous attributes from COMMAND_EXECUTION_ATTRIBUTE
` list, usually they can be exploited for arbitrary code execution. e.g __import__("os").system("ls")
.
Code Snippet:
5. Ensuring Specific Solution Format
The validation ensures the presence of a specific function or variable to represent the solution. If it's absent, the code won't be executed.
Example of solution function:
Code Snippet:
6. Execution Timeout
To prevent Denial of Service attacks in form of infinite loops or excessively long computations, a timeout is enforced during execution. The timeout
parameter ensures that the execution is halted if it's exceeded.
Code Snippet:
Exploitation
Based on the protections outlined above, we face the following limitations on code execution:
The executed code must adhere to the expected format, such as being encapsulated in a function with an appropriate name.
It is not possible to directly call any forbidden functions or access prohibited attributes.
Directly importing basic libraries, such as os, subprocess, and similar modules, is restricted.
As a result of these constraints, most documented payloads are unlikely to function as intended. Our team decided to proceed with a relatively stable method for bypassing AST restrictions - class pollution.
What is Class Pollution?
In Python, class pollution refers to the unauthorized or malicious modification of a class's attributes or methods. This may involve dynamically adding new methods, altering existing ones, or injecting harmful behavior. Class pollution takes advantage of Python's dynamic nature, where classes and objects can be modified during runtime. This can introduce vulnerabilities, especially when the code interacts with untrusted inputs or executes dynamic code.
Proof of Concept

The payload defines a custom exception class A, inheriting from Exception, with an overloaded __add__
method that simply returns 1 when the + operator is used. However, the payload dynamically replaces the __add__
method of class A with Python's built-in exec
function, effectively "polluting" the class by altering its behavior. In the try block, an exception of type A is raised and caught in the corresponding except block, where the + operator is applied to the caught exception object (a) and a string containing Python code import os; os.system("id")
. Because A.__add__
has been replaced with exec
, this operation does not perform addition but instead executes the string as Python code.
Conclusion
Mitigating every possible vector for malicious code execution is an extremely challenging task, as attackers continuously find creative ways to exploit dynamic features and unforeseen vulnerabilities. While implementing essential limitations and guardrails is important, it is even more critical, especially in the context of AI agents, to focus on creating a secure and controlled execution environment. A robust solution is the use of sandboxing, which isolates code execution from the underlying system. This can be easily achieved with tools like Docker containers or through third-party providers specializing in secure execution environments. By employing sandboxing, even if malicious code is executed, its impact is contained, protecting the broader system from compromise.