PraisonAI: MCP HTTP-stream transport is unauthenticated by default, exposing tool enumeration and an unvalidated tool-call surface
What fixes it
- 4.6.78praisonai, pipGitHub
- 4.6.78PraisonAICVE List
- 4.6.78praisonai, PyPIOSV
pip install "praisonai>=4.6.78"
A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.
Description
Summary
PraisonAI's MCP HTTP-stream server authenticates requests only when an API key is configured; the CLI defaults --api-key to None, so praisonai mcp serve --transport http-stream exposes the full MCP surface unauthenticated. A request with no Authorization (and no Origin) can initialize and tools/list (~50 tools), and the dispatcher forwards tool-call arguments to handlers without validating them against the advertised inputSchema. Runtime-confirmed for unauthenticated initialize/tools/list and the dispatcher schema-bypass. This is not an RCE/file-read in 4.6.63 — workflow.run/workflow.run_file are runtime-refuted (adapter regression). Severity Medium–High.
Details
Affected component
- Package:
praisonai4.6.63. Files:src/praisonai/praisonai/mcp_server/transports/http_stream.py,mcp_server/cli.py,mcp_server/server.py(dispatcher).
Vulnerable code / root cause
Path: src/praisonai/praisonai/mcp_server/transports/http_stream.py
Function: mcp_post / _validate_origin
Snippet:
if self.api_key: # auth applied ONLY when api_key is set
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer ") or auth_header[7:] != self.api_key:
return JSONResponse({"error": "Unauthorized"}, status_code=401)
# _validate_origin: returns True when the Origin header is absentIssue: with api_key=None, no auth check runs; a missing Origin header is allowed, so non-browser clients (curl/Burp) are not blocked.
Path: src/praisonai/praisonai/mcp_server/cli.py
Function: cmd_serve (argparse)
Snippet:
parser.add_argument("--api-key", default=None) # unauthenticated by defaultPath: src/praisonai/praisonai/mcp_server/server.py
Function: _handle_tools_call
Snippet:
result = await tool.handler(**arguments) # arguments forwarded without inputSchema validation
Issue: attacker-controlled arguments are passed straight to the handler; the dispatcher does not validate them against the tool's advertised inputSchema. The only thing rejecting undeclared keys is the handler's own Python signature.
Attack flow
- Operator runs
praisonai mcp serve --transport http-stream(no--api-key). - Attacker (no auth, no Origin) sends
initialize→ session;tools/list→ enumerates ~50 tools;tools/call→ arguments pass through unvalidated.
Why existing protection is bypassed
Auth is opt-in (only added when an api key is set); missing Origin is allowed; the dispatcher does not enforce inputSchema.
Security boundary
Unauthenticated access to the MCP tool surface. Default bind 127.0.0.1 (any local process / multi-user host; remote only if --host 0.0.0.0).
Scope limits (do not overclaim)
praisonai.workflow.run/workflow.run_fileare runtime-refuted in 4.6.63: the adapter callsAgentsGenerator(...)missing the requiredconfig_listargument → errors before any execution/file open. Several other tool adapters also error at runtime. No unauthenticated RCE/arbitrary-file-open via these tools at HEAD.- MCP
knowledge.addfile read is broken (seeFT-01_Knowledge_FileRead_Negative_Report.md).
Proof of Concept
Environment
Real MCP HTTP-stream server (api_key=None) in a local runtime (127.0.0.1:18090). Runnable assets: PraisonAI-Runtime-Repro\runtime-files\ (docker-compose.mcp.yml). MCP requests use Accept: application/json + header Mcp-Session-Id.
Steps to reproduce
MCP-Initialize:POST /mcpinitialize (no Authorization) →200+mcp-session-id.MCP-Tools-List-NoAuth:POST /mcptools/listwith that session id →200+ ~50 tools.MCP-Schema-Bypass:tools/callwith an undeclared extra argument (__undeclared_evil_param__).
Expected result
The transport requires authentication; the dispatcher validates arguments against inputSchema.
Actual result
initialize/tools/listsucceed with no auth and no Origin header.- The undeclared argument reaches the handler (
got an unexpected keyword argument '__undeclared_evil_param__'), proving no schema validation at the dispatcher.
Screenshots
<img width="1544" height="798" alt="03-MCP-Schema-Bypass" src="https://github.com/user-attachments/assets/5a4cb764-9428-487d-b4e0-2854cbda7fb7" /> <img width="1538" height="793" alt="02-MCP-Tools-List-NoAuth" src="https://github.com/user-attachments/assets/6356af71-867f-4fbc-a994-c7ca338fd2aa" />
Screenshots
Unauthenticated MCP initialize
A POST request to /mcp with method initialize succeeds without an Authorization header. The server returns HTTP 200 OK, exposes MCP capabilities, and issues an mcp-session-id to the unauthenticated client.
<img width="1546" height="804" alt="01-MCP-Initialize-NoAuth" src="https://github.com/user-attachments/assets/2a62ee6b-99d3-4a38-a752-bfe6165c8c04" />
Unauthenticated MCP tools/list
After initialization, the same unauthenticated MCP session can call tools/list using only the issued Mcp-Session-Id. The server returns HTTP 200 OK and exposes tool names, schemas, and annotations.
<img width="1538" height="793" alt="02-MCP-Tools-List-NoAuth" src="https://github.com/user-attachments/assets/f55189ff-13aa-4c36-a617-3d2ee4a52a84" />
MCP tool-call schema bypass
The unauthenticated MCP client calls tools/call with an extra argument not declared in the tool schema. Instead of rejecting the schema-violating input at the dispatcher layer, the unexpected parameter reaches the Python handler and causes an unexpected keyword argument error. This confirms incomplete input-schema enforcement for tool calls.
<img width="1544" height="798" alt="03-MCP-Schema-Bypass" src="https://github.com/user-attachments/assets/d3f36e50-2363-4eb2-8b3c-985ff0e27f6e" />
Impact
Unauthenticated tool enumeration and tool-call surface; LLM-key/cost abuse and data access via whichever tools function (impact currently limited by several broken adapters and the default loopback bind). No confirmed unauthenticated RCE/file-read in 4.6.63.
Text from GitHub Advisory Database
Affected versions 54
- praisonai, pip<= 4.6.77GitHub
- PraisonAIaffected: all versions before 4.6.78CVE List
- PraisonAIunaffected: 4.6.78CVE List
- praisonai, PyPIfrom first release, fixed in 4.6.78OSV
- praisonai, PyPI0.0.1OSV
- praisonai, PyPI0.0.10OSV
- praisonai, PyPI0.0.11OSV
- praisonai, PyPI0.0.12OSV
- praisonai, PyPI0.0.13OSV
- praisonai, PyPI0.0.14OSV
- praisonai, PyPI0.0.15OSV
- praisonai, PyPI0.0.16OSV
- praisonai, PyPI0.0.17OSV
- praisonai, PyPI0.0.18OSV
- praisonai, PyPI0.0.19OSV
- praisonai, PyPI0.0.2OSV
- praisonai, PyPI0.0.20OSV
- praisonai, PyPI0.0.21OSV
- praisonai, PyPI0.0.22OSV
- praisonai, PyPI0.0.23OSV
- praisonai, PyPI0.0.24OSV
- praisonai, PyPI0.0.25OSV
- praisonai, PyPI0.0.26OSV
- praisonai, PyPI0.0.27OSV
- praisonai, PyPI0.0.28OSV
- praisonai, PyPI0.0.29OSV
- praisonai, PyPI0.0.3OSV
- praisonai, PyPI0.0.30OSV
- praisonai, PyPI0.0.31OSV
- praisonai, PyPI0.0.32OSV
- praisonai, PyPI0.0.33OSV
- praisonai, PyPI0.0.34OSV
- praisonai, PyPI0.0.35OSV
- praisonai, PyPI0.0.36OSV
- praisonai, PyPI0.0.37OSV
- praisonai, PyPI0.0.38OSV
- praisonai, PyPI0.0.39OSV
- praisonai, PyPI0.0.4OSV
- praisonai, PyPI0.0.40OSV
- praisonai, PyPI0.0.41OSV
- praisonai, PyPI0.0.42OSV
- praisonai, PyPI0.0.43OSV
- praisonai, PyPI0.0.44OSV
- praisonai, PyPI0.0.45OSV
- praisonai, PyPI0.0.46OSV
- praisonai, PyPI0.0.47OSV
- praisonai, PyPI0.0.48OSV
- praisonai, PyPI0.0.49OSV
- praisonai, PyPI0.0.5OSV
- praisonai, PyPI0.0.50OSV
- praisonai, PyPI0.0.52OSV
- praisonai, PyPI0.0.53OSV
- praisonai, PyPI0.0.54OSV
- praisonai, PyPI0.0.55OSV
References 5
- vulncheck.com/advisories/praisonai-before-authentication-bypass-via-http-streamAdvisoryvia CVE List
- github.com/advisories/GHSA-hc5v-gxvj-58whAdvisoryvia GitHub
- github.com/MervinPraison/PraisonAI/security/advisories/GHSA-hc5v-gxvj-58whExploitvia CVE List
- nvd.nist.gov/vuln/detail/CVE-2026-61427Referencevia GitHub
- nvd.nist.gov/vuln/detail/CVE-2026-62178Referencevia GitHub