CVE-2025-51482
Letta-ai Letta vulnerable to RCE via sandbox bypass in the tools/run API.
- CVSS 8.8
- CWE-94
- Input Validation and Sanitization
- Remote
Remote Code Execution in letta.server.rest_api.routers.v1.tools.run_tool_from_source in letta-ai Letta 0.7.12 allows remote attackers to execute arbitrary Python code and system commands via crafted payloads to the /v1/tools/run endpoint, bypassing intended sandbox restrictions.
- CWE
- CWE-94
- CVSS base score
- 8.8
- Published
- 2025-07-22
- OWASP
- A03 Injection
- Orthogonal defect classification
- Checking
- Code defect classification
- Incorrect Check
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Fixed by upgrading
- Yes
Solution
Upgrade to Letta version 0.8.0 or later.
Vulnerable code sample
import io
import contextlib
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
# This is a hypothetical representation of the letta.server.rest_api application.
# The actual file structure would be more complex (e.g., letta/server/rest_api/routers/v1/tools.py)
# but the core vulnerable logic is represented here.
app = FastAPI(
title="Letta AI - Vulnerable Version 0.7.12",
description="An example demonstrating CVE-2025-51482.",
)
class ToolRunRequest(BaseModel):
# The user provides raw Python source code to be executed.
source_code: str
# This function contains the vulnerability.
# It attempts to run user-provided code in a "sandboxed" environment.
def run_tool_from_source(code: str):
"""
Executes Python code from a string source.
Intended to be sandboxed, but the implementation is flawed.
"""
# Attempt at a sandbox: provide a dictionary with restricted built-ins.
# The goal is to prevent access to dangerous functions like `open` or `os.system`.
# However, this sandbox is easily bypassed.
restricted_globals = {
"__builtins__": {
"print": print,
"range": range,
"list": list,
"dict": dict,
"str": str,
"int": int,
"float": float,
"True": True,
"False": False,
"None": None,
}
}
# Use StringIO to capture the output (stdout) of the executed code.
output_capture = io.StringIO()
try:
with contextlib.redirect_stdout(output_capture):
# The vulnerable call: exec() is used with an easily bypassable sandbox.
# An attacker can use object introspection to break out and access any module.
# Example payload:
# [c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'BuiltinImporter'][0]().load_module('os').system('id')
exec(code, restricted_globals)
result = output_capture.getvalue()
return {"status": "success", "output": result}
except Exception as e:
# Return any exceptions that occur during execution.
return {"status": "error", "output": str(e)}
@app.post("/v1/tools/run")
async def run_tool_endpoint(request: ToolRunRequest):
"""
API endpoint that exposes the vulnerable run_tool_from_source function.
Attackers can send crafted Python code to this endpoint for execution.
"""
if not request.source_code:
raise HTTPException(status_code=400, detail="source_code cannot be empty.")
# The endpoint directly calls the vulnerable function with user-provided data.
execution_result = run_tool_from_source(request.source_code)
return execution_resultPatched code sample
import io
import sys
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from RestrictedPython import compile_restricted, safe_builtins
# This code represents a hypothetical fix for a vulnerability like CVE-2025-51482.
# The original vulnerability would have likely used `exec()` or `eval()` on unsanitized
# user input, allowing arbitrary code execution.
#
# The fix replaces the direct, unsafe execution with a robust sandboxing mechanism
# using the 'RestrictedPython' library. This library compiles the user-provided code
# in a restricted mode, preventing access to dangerous modules, built-ins, and
# system-level functions.
app = FastAPI()
class ToolPayload(BaseModel):
source_code: str
tool_args: dict = {}
# Define a set of safe global variables that can be passed to the executed code.
# This prevents the code from accessing unsafe modules like 'os' or 'subprocess'.
# We can also add custom, safe functions here if needed.
restricted_globals = {
"__builtins__": safe_builtins,
# Example of a safe utility function that could be exposed to the tool
"safe_print": print
}
@app.post("/v1/tools/run")
async def run_tool_from_source(payload: ToolPayload):
"""
Executes a tool from a source code string within a secure sandbox.
"""
source_code = payload.source_code
if not source_code.strip():
raise HTTPException(status_code=400, detail="source_code cannot be empty.")
# A dictionary to hold the local scope of the executed code,
# including arguments and results.
local_scope = {"args": payload.tool_args}
# Use a string IO to capture any output from the sandboxed code
output_buffer = io.StringIO()
original_stdout = sys.stdout
sys.stdout = output_buffer
try:
# 1. Compile the code in a restricted environment.
# This is the core of the security fix. `compile_restricted` will
# raise a SyntaxError if the code uses disallowed constructs
# (e.g., `import os`, opening files, accessing private attributes).
byte_code = compile_restricted(
source_code,
filename='<user_tool>',
mode='exec'
)
# 2. Execute the compiled bytecode.
# The execution is performed with the restricted globals and a
# specific local scope, preventing access to the wider application's state.
exec(byte_code, restricted_globals, local_scope)
except Exception as e:
# If compilation or execution fails, restore stdout and raise an error.
sys.stdout = original_stdout
raise HTTPException(
status_code=400,
detail=f"Error executing tool: {e}"
)
finally:
# Always restore the original stdout
sys.stdout = original_stdout
captured_output = output_buffer.getvalue()
# The result of the operation is expected to be in the 'result'
# variable within the executed code's local scope.
result = local_scope.get("result", None)
return {
"status": "success",
"result": result,
"output": captured_output
}
# Example of how to run this FastAPI application with uvicorn:
# uvicorn your_file_name:app --reload
#
# To test the fix:
#
# 1. Benign Payload (Should succeed):
# curl -X POST http://127.0.0.1:8000/v1/tools/run -H "Content-Type: application/json" -d \
# '{"source_code": "x = args.get(\"a\", 0) + args.get(\"b\", 0)\nresult = {\"sum\": x}", "tool_args": {"a": 5, "b": 10}}'
#
# 2. Malicious Payload (Should be blocked by RestrictedPython and fail):
# curl -X POST http://127.0.0.1:8000/v1/tools/run -H "Content-Type: application/json" -d \
# '{"source_code": "import os\nos.system(\"echo pwned > /tmp/pwned\")\nresult = \"failed\""}'
#
# The above request will result in an HTTP 400 error with a detail message like:
# "SyntaxError: ('Line 1: import os is not allowed.', ('<user_tool>', 1, 0, 'import os', 1, 10))"Payload
{
"source_code": "print([c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'Popen'][0](['/bin/sh', '-c', 'id'], stdout=-1).communicate()[0].decode())"
}
Cite this entry
@misc{vaitp:cve202551482,
title = {{Letta-ai Letta vulnerable to RCE via sandbox bypass in the tools/run API.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-51482},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-51482/}}
}
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 ::
