PraisonAI has an origin validation bypass in MCP HTTP Stream transport that allows browser-mediated unauthenticated tool execution on local MCP server (GHSA-wj6g-v78p-6fx3) · Sieve
중간취약점
PraisonAI has an origin validation bypass in MCP HTTP Stream transport that allows browser-mediated unauthenticated tool execution on local MCP server
When allowed_origins is not explicitly configured and the server binds to localhost, the transport allowlist includes bare localhost origins:
if allowed_origins is None:
if host in ("127.0.0.1", "localhost", "::1"):
self.allowed_origins = [
"http://localhost",
"http://127.0.0.1",
"https://localhost",
"https://127.0.0.1",
]
The vulnerable validation accepts origins that merely start with an allowlisted value:
for allowed in self.allowed_origins:
if request_origin == allowed or request_origin.startswith(allowed):
return True
Because http://localhost.evil.example starts with http://localhost, it is accepted as a trusted localhost origin.
Authentication is only enforced if an API key is configured:
if self.api_key:
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,
)
The request body is then parsed and dispatched to the MCP server:
body = await request.json()
response = await self.server.handle_message(body)
The MCP server handles tools/call by looking up the named tool and invoking the registered handler with attacker-controlled arguments:
tool_name = params.get("name")
arguments = params.get("arguments", {})
tool = self._tool_registry.get(tool_name)
if asyncio.iscoroutinefunction(tool.handler):
result = await tool.handler(**arguments)
else:
result = tool.handler(**arguments)
Another exposed MCP tool, praisonai.todo.add, writes attacker-supplied content into local PraisonAI state at ~/.praison/todo.json.
PoC
The following local PoC verifies the vulnerable Origin logic and unauthenticated MCP tool execution without contacting any external provider. It uses a fake in-memory litellm module so the file-read effect is captured locally and safely.
Run from the repository root with test dependencies installed:
Origin: https://evil.example is rejected with 403.
Origin: http://localhost.evil.example is accepted with 200.
The bypassed request invokes praisonai.files.create and reads the local safe marker file.
The bypassed request invokes praisonai.todo.add and writes local PraisonAI state.
Impact
A malicious webpage can bypass the localhost Origin allowlist and trigger MCP tools/call requests against a locally running unauthenticated HTTP Stream server.
In local testing, this allowed invoking registered PraisonAI tools that:
read an attacker-selected local file path and pass the file handle to the configured LiteLLM provider; and
modify local PraisonAI state by writing to ~/.praison/todo.json.
The default MCP HTTP Stream bind address is localhost, so exploitation is browser-mediated. A practical attack requires the victim to run the HTTP Stream MCP server without an API key and visit an attacker-controlled origin that matches the prefix bypass, or a DNS-rebinding-style setup. If an API key is configured, exploitability is significantly reduced.