Unauthenticated PraisonAI UI MCP connect endpoint executes attacker-chosen local commands
Summary
PraisonAI v4.6.48 exposes the PraisonAIUI MCP client management API through the default UI host apps without authentication. A remote unauthenticated client can send POST /api/mcp/connect with a command and args field. The endpoint passes those values into the MCP stdio client, which starts the attacker-selected local process as the PraisonAI UI service user.
The issue is reachable through PraisonAI's hosted UI integration (praisonai ui, praisonai ui agents, praisonai claw, and any app using praisonai.integration.host_app.create_host_app() / build_host_app()). praisonai ui and related Typer UI commands bind to 0.0.0.0 by default.
Affected Versions
Confirmed affected:
praisonai v4.6.48
- Commit tested:
d5f1114aaf1a2e9f121a6e66b929149ca2201f1d
- Tag tested:
v4.6.48
- Pinned UI dependency:
aiui==0.3.121 from src/praisonai/uv.lock
Likely affected:
- Any PraisonAI release that exposes
aiui / praisonaiui create_app() through the PraisonAI UI host apps without authentication and includes the mcp dependency. I only confirmed the latest release during this audit.
Severity
Reasoning:
AV: the vulnerable endpoint is an HTTP API route.
AC: a single POST request is sufficient.
PR: default UI host apps do not require credentials unless opt-in auth is configured.
UI: no victim interaction is needed after the server is running.
S: code executes in the PraisonAI UI server process context.
C/I/A: arbitrary local command execution permits secret exfiltration, file tampering, and service disruption.
Root Cause
PraisonAI depends on MCP by default and exposes PraisonAIUI via optional UI extras:
src/praisonai/pyproject.toml:11 includes base dependencies.
src/praisonai/pyproject.toml:19 includes mcp>=1.20.0.
src/praisonai/pyproject.toml:25 defines the ui extra with aiui>=0.3.121,<0.4.
src/praisonai/pyproject.toml:197 defines the claw extra with aiui[all]>=0.3.121,<0.4.
PraisonAI's UI commands bind externally by default and launch aiui run:
src/praisonai/praisonai/cli/commands/ui.py:114 sets host="0.0.0.0" for praisonai ui.
src/praisonai/praisonai/cli/commands/ui.py:163 passes that host to aiui run.
src/praisonai/praisonai/cli/commands/ui.py:186, :204, and :222 also default subcommands to 0.0.0.0.
src/praisonai/praisonai/cli/commands/claw.py:41 defines the full dashboard command.
src/praisonai/praisonai/cli/commands/claw.py:93 launches aiui run with the selected host.
PraisonAI's default apps create the PraisonAIUI Starlette app without forcing authentication:
src/praisonai/praisonai/ui_chat/default_app.py:18 calls configure_host(...).
src/praisonai/praisonai/ui_chat/default_app.py:142 exports app = create_host_app().
src/praisonai/praisonai/claw/default_app.py:63 calls configure_host(...).
src/praisonai/praisonai/claw/default_app.py:128 exports app = create_host_app().
src/praisonai/praisonai/integration/host_app.py:174 imports praisonaiui.server.create_app.
src/praisonai/praisonai/integration/host_app.py:180 returns create_app().
In aiui==0.3.121, the exposed server registers the MCP routes and auth is opt-in:
praisonaiui/server.py:1483 defines api_mcp_connect.
praisonaiui/server.py:1488 reads attacker-controlled JSON.
praisonaiui/server.py:1491 accepts either command or url.
praisonaiui/server.py:1496 calls connect_mcp_server(body).
praisonaiui/server.py:2516 defines create_app(..., require_auth=False, ...).
praisonaiui/server.py:2550 adds AuthEnforcementMiddleware, but it only enforces auth when AUTH_ENFORCE=true.
praisonaiui/server.py:2769 registers /api/mcp/servers.
praisonaiui/server.py:2770 registers /api/mcp/connect.
The MCP feature converts the request body into a local process launch:
praisonaiui/features/mcp.py:325 defines connect_server(self, server_config).
praisonaiui/features/mcp.py:330 chooses stdio transport when command is present.
praisonaiui/features/mcp.py:332 constructs StdioMCPClient(command=server_config["command"], args=server_config.get("args", [])).
praisonaiui/features/mcp.py:360 calls client.connect(), which invokes the MCP stdio transport and starts the process.
Minimal PoC
PoC file: poc/praisonai-aiui-mcp-connect-rce.py
The PoC runs the PraisonAI host app in-process, sends the unauthenticated HTTP request, and asks the server to execute /usr/bin/touch /tmp/praisonai_host_app_mcp_touch_marker.txt. It does not contact an LLM provider and uses no credentials.
Observed output from the tested checkout with aiui==0.3.121 and mcp==1.25.0 available:
[19:19:55] server.py:229 WARNING No auth_token provided for Gateway server. Generated temporary token: gw_****650a. For production, set GATEWAY_AUTH_TOKEN.
[19:19:55] mcp.py:135 ERROR Failed to connect to MCP stdio server: 'tuple' object has no attribute 'initialize'
HTTP_STATUS= 200
RESPONSE= {"server":{"name":"poc-stdio-process-0","transport":"stdio","status":"error","tools":[],"last_error":"Connection failed"}}
SUCCESS_AT_ATTEMPT= 0
MARKER_EXISTS= True
MARKER_PATH= /tmp/praisonai_host_app_mcp_touch_marker.txt
The MCP handshake fails because aiui==0.3.121 is not compatible with the locked mcp==1.25.0 return shape, but the attacker-selected process is already started. Process startup can race the immediate teardown caused by this version mismatch, so the checked-in PoC retries the same unauthenticated request until /usr/bin/touch wins scheduling and creates the marker. The marker file proves local command execution despite the reported MCP connection error.
Exploit Scenario
An operator runs:
pip install "praisonai[ui]"
praisonai ui
Because praisonai ui binds to 0.0.0.0 by default and the generated app does not require authentication by default, any host that can reach the UI port can send:
POST /api/mcp/connect
Content-Type: application/json
{
"name": "evil",
"command": "/usr/bin/touch",
"args": ["/tmp/pwned-by-ui-mcp"]
}
In a real attack, the command can be replaced with a shell, a credential exfiltration command, a file modification command, or a payload that starts a long-lived process as the PraisonAI UI server user.
Novelty / Non-Duplicate Analysis
Searched sources:
- OSV query for PyPI
praisonai: 51 advisories returned.
- OSV query for PyPI
aiui: 0 advisories returned.
- OSV query for PyPI
praisonaiui: 0 advisories returned.
- GitHub Advisory Database search for exact
/api/mcp/connect, api_mcp_connect, StdioMCPClient, connect_mcp_server, and praisonaiui.features.mcp.
- NVD API searches for
PraisonAI StdioMCPClient, PraisonAI api_mcp_connect, PraisonAI /api/mcp/connect, PraisonAIUI /api/mcp/connect, and aiui StdioMCPClient: 0 results.
- GitHub issue/PR searches in
MervinPraison/PraisonAI for exact endpoint/function/class terms. Only one unrelated PR was returned for /api/mcp/connect; no issue/PR matched api_mcp_connect, StdioMCPClient, connect_mcp_server, or praisonaiui.features.mcp.
- Broad web searches for exact endpoint, file, class, and function terms returned no matching public vulnerability report.
Why this is distinct from known PraisonAI advisories:
- Not the excluded
praisonai serve agents --api-key /agents auth bypass. This report targets POST /api/mcp/connect in the PraisonAIUI host app.
- Not GHSA-9gm9-c8mq-vq7m / CVE-2026-34935 or GHSA-9qhq-v63v-fv3j / CVE-2026-41497. Those involve
MCPHandler.parse_mcp_command() command parsing. This finding uses praisonaiui.server.api_mcp_connect -> praisonaiui.features.mcp.connect_mcp_server -> StdioMCPClient.
- Not GHSA-pj2r-f9mw-vrcq / CVE-2026-40159. That advisory concerns sensitive environment variables inherited by untrusted MCP subprocesses. This finding is unauthenticated network-triggered local process execution.
- Not GHSA-6rmh-7xcm-cpxj or GHSA-8444-4fhq-fxpq. Those concern unauthenticated legacy/generated agent servers. This is a distinct UI route and a distinct sink that starts arbitrary local processes.
- Not GHSA-9cr9-25q5-8prj, GHSA-9mqq-jqxf-grvw, or other MCP server file-read/path-traversal advisories. This path is the UI MCP client connector, not PraisonAI's MCP server tool dispatcher.
Recommended Fix
- Remove arbitrary
command/args from the remote HTTP API. MCP stdio servers should be configured only from trusted local configuration, not caller-supplied JSON.
- Require authentication and authorization on
/api/mcp/connect, /api/mcp/disconnect/*, and /api/mcp/servers regardless of AUTH_ENFORCE.
- Change UI command defaults from
0.0.0.0 to 127.0.0.1, or require an explicit --unsafe-expose style flag when binding externally without auth.
- If remote MCP registration is a required feature, allow only URL-based transports with SSRF protections, or maintain an administrator-configured allowlist of commands.
- Add regression tests that unauthenticated requests to
/api/mcp/connect cannot start a subprocess, including when AUTH_ENFORCE is unset.
References
Unauthenticated PraisonAI UI MCP connect endpoint executes attacker-chosen local commands
Summary
PraisonAI v4.6.48 exposes the PraisonAIUI MCP client management API through the default UI host apps without authentication. A remote unauthenticated client can send
POST /api/mcp/connectwith acommandandargsfield. The endpoint passes those values into the MCP stdio client, which starts the attacker-selected local process as the PraisonAI UI service user.The issue is reachable through PraisonAI's hosted UI integration (
praisonai ui,praisonai ui agents,praisonai claw, and any app usingpraisonai.integration.host_app.create_host_app()/build_host_app()).praisonai uiand related Typer UI commands bind to0.0.0.0by default.Affected Versions
Confirmed affected:
praisonaiv4.6.48d5f1114aaf1a2e9f121a6e66b929149ca2201f1dv4.6.48aiui==0.3.121fromsrc/praisonai/uv.lockLikely affected:
aiui/praisonaiuicreate_app()through the PraisonAI UI host apps without authentication and includes themcpdependency. I only confirmed the latest release during this audit.Severity
Reasoning:
AV: the vulnerable endpoint is an HTTP API route.AC: a single POST request is sufficient.PR: default UI host apps do not require credentials unless opt-in auth is configured.UI: no victim interaction is needed after the server is running.S: code executes in the PraisonAI UI server process context.C/I/A: arbitrary local command execution permits secret exfiltration, file tampering, and service disruption.Root Cause
PraisonAI depends on MCP by default and exposes PraisonAIUI via optional UI extras:
src/praisonai/pyproject.toml:11includes base dependencies.src/praisonai/pyproject.toml:19includesmcp>=1.20.0.src/praisonai/pyproject.toml:25defines theuiextra withaiui>=0.3.121,<0.4.src/praisonai/pyproject.toml:197defines theclawextra withaiui[all]>=0.3.121,<0.4.PraisonAI's UI commands bind externally by default and launch
aiui run:src/praisonai/praisonai/cli/commands/ui.py:114setshost="0.0.0.0"forpraisonai ui.src/praisonai/praisonai/cli/commands/ui.py:163passes that host toaiui run.src/praisonai/praisonai/cli/commands/ui.py:186,:204, and:222also default subcommands to0.0.0.0.src/praisonai/praisonai/cli/commands/claw.py:41defines the full dashboard command.src/praisonai/praisonai/cli/commands/claw.py:93launchesaiui runwith the selected host.PraisonAI's default apps create the PraisonAIUI Starlette app without forcing authentication:
src/praisonai/praisonai/ui_chat/default_app.py:18callsconfigure_host(...).src/praisonai/praisonai/ui_chat/default_app.py:142exportsapp = create_host_app().src/praisonai/praisonai/claw/default_app.py:63callsconfigure_host(...).src/praisonai/praisonai/claw/default_app.py:128exportsapp = create_host_app().src/praisonai/praisonai/integration/host_app.py:174importspraisonaiui.server.create_app.src/praisonai/praisonai/integration/host_app.py:180returnscreate_app().In
aiui==0.3.121, the exposed server registers the MCP routes and auth is opt-in:praisonaiui/server.py:1483definesapi_mcp_connect.praisonaiui/server.py:1488reads attacker-controlled JSON.praisonaiui/server.py:1491accepts eithercommandorurl.praisonaiui/server.py:1496callsconnect_mcp_server(body).praisonaiui/server.py:2516definescreate_app(..., require_auth=False, ...).praisonaiui/server.py:2550addsAuthEnforcementMiddleware, but it only enforces auth whenAUTH_ENFORCE=true.praisonaiui/server.py:2769registers/api/mcp/servers.praisonaiui/server.py:2770registers/api/mcp/connect.The MCP feature converts the request body into a local process launch:
praisonaiui/features/mcp.py:325definesconnect_server(self, server_config).praisonaiui/features/mcp.py:330chooses stdio transport whencommandis present.praisonaiui/features/mcp.py:332constructsStdioMCPClient(command=server_config["command"], args=server_config.get("args", [])).praisonaiui/features/mcp.py:360callsclient.connect(), which invokes the MCP stdio transport and starts the process.Minimal PoC
PoC file:
poc/praisonai-aiui-mcp-connect-rce.pyThe PoC runs the PraisonAI host app in-process, sends the unauthenticated HTTP request, and asks the server to execute
/usr/bin/touch /tmp/praisonai_host_app_mcp_touch_marker.txt. It does not contact an LLM provider and uses no credentials.Observed output from the tested checkout with
aiui==0.3.121andmcp==1.25.0available:The MCP handshake fails because
aiui==0.3.121is not compatible with the lockedmcp==1.25.0return shape, but the attacker-selected process is already started. Process startup can race the immediate teardown caused by this version mismatch, so the checked-in PoC retries the same unauthenticated request until/usr/bin/touchwins scheduling and creates the marker. The marker file proves local command execution despite the reported MCP connection error.Exploit Scenario
An operator runs:
pip install "praisonai[ui]" praisonai uiBecause
praisonai uibinds to0.0.0.0by default and the generated app does not require authentication by default, any host that can reach the UI port can send:In a real attack, the command can be replaced with a shell, a credential exfiltration command, a file modification command, or a payload that starts a long-lived process as the PraisonAI UI server user.
Novelty / Non-Duplicate Analysis
Searched sources:
praisonai: 51 advisories returned.aiui: 0 advisories returned.praisonaiui: 0 advisories returned./api/mcp/connect,api_mcp_connect,StdioMCPClient,connect_mcp_server, andpraisonaiui.features.mcp.PraisonAI StdioMCPClient,PraisonAI api_mcp_connect,PraisonAI /api/mcp/connect,PraisonAIUI /api/mcp/connect, andaiui StdioMCPClient: 0 results.MervinPraison/PraisonAIfor exact endpoint/function/class terms. Only one unrelated PR was returned for/api/mcp/connect; no issue/PR matchedapi_mcp_connect,StdioMCPClient,connect_mcp_server, orpraisonaiui.features.mcp.Why this is distinct from known PraisonAI advisories:
praisonai serve agents --api-key/agentsauth bypass. This report targetsPOST /api/mcp/connectin the PraisonAIUI host app.MCPHandler.parse_mcp_command()command parsing. This finding usespraisonaiui.server.api_mcp_connect -> praisonaiui.features.mcp.connect_mcp_server -> StdioMCPClient.Recommended Fix
command/argsfrom the remote HTTP API. MCP stdio servers should be configured only from trusted local configuration, not caller-supplied JSON./api/mcp/connect,/api/mcp/disconnect/*, and/api/mcp/serversregardless ofAUTH_ENFORCE.0.0.0.0to127.0.0.1, or require an explicit--unsafe-exposestyle flag when binding externally without auth./api/mcp/connectcannot start a subprocess, including whenAUTH_ENFORCEis unset.References