# PraisonAI ToolsMCPServer legacy SSE transport accepts attacker Host/Origin and exposes registered tools

PraisonAI's legacy SSE MCP server lacks Origin/Host/auth checks, letting a malicious site DNS-rebind and invoke registered local tools as the user.

- Payout: Research
- Severity: high
- Program: PraisonAI
- Found by: rexpository
- Disclosed: 2026-06-18
- CVE: CVE-2026-57112
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-vmf9-xx9w-86wx
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-vmf9-xx9w-86wx
- Tags: area:ai domain:ai domain:web impact:internal-access kind:research platform:ghsa sev:high tech:python technique:chained technique:source-review vuln:access-control vuln:auth-bypass vuln:host-header vuln:misconfiguration

**Target:** Starlette MCP HTTP+SSE server (/sse, /messages/) via ToolsMCPServer.run_sse
**Root cause:** ToolsMCPServer.run_sse() mounts SseServerTransport on a bare Starlette app with no Origin, Host, or authentication middleware, unlike the Streamable HTTP transport which enforces an Origin guard.
**Steps:**
1. Start a legacy SSE MCP server: `praisonai mcp run <name> --transport sse` or `ToolsMCPServer(...).run_sse(host, port)`, binding 127.0.0.1.
2. Send a request to `/sse` with attacker-controlled headers `Host: attacker.example.test:62380` and `Origin: http://attacker.example.test:62380`.
3. Open the SSE stream, list tools (e.g. `record_marker`), and POST a tool call to `/messages/` with the same attacker headers.
4. Observe the tool executes and writes the marker (`recorded:executed-from-attacker-origin`).
5. Control: same attacker Origin against the Streamable HTTP transport returns HTTP 403.
**How found:** Source review of the SSE wrapper versus the Streamable HTTP transport: noticed the sibling transport had no Origin/Host/auth checks while the newer one returned 403 for bad Origin; confirmed with a PoV that monkey-patches uvicorn.run to capture the app and probes it with attacker headers.
**Exploitation:** Attacker hosts a malicious page; victim visits it; DNS rebinding points the attacker domain at 127.0.0.1 so browser requests reach the local SSE MCP server with attacker Host/Origin. Attacker enumerates registered tools and invokes them as the local user, reaching whatever files, repos, cloud APIs or internal services those tools touch.
**Escalation:** Impact scales with the configured tool set: file/repo access, issue trackers, cloud APIs, internal services.
**Insight:** When a project ships two transports for the same protocol, diff their security middleware: the newer path's Origin/Host guard is a checklist for the legacy sibling, and a 403 control on one transport proves the other's silence is a gap, not intent.

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

**PoV command running SSE Host/Origin bypass script**

```
uv run --with mcp --with starlette --with uvicorn --with httpx --with anyio \
  python submission-bundle/praisonai-prai-cand-015-mcp-sse-host-origin-bypass/poc/pov_prai_cand_015_sse_mcp_host_origin_bypass.py \
  --repo-src artifacts/repos/praisonai-v4.6.58/src
```

**PoV result showing attacker Host/Origin tool invocation**

```
"sse_probe": {
    "attacker_headers": {
      "Host": "attacker.example.test:62380",
      "Origin": "http://attacker.example.test:62380"
    },
    "bind_host": "127.0.0.1",
    "marker_value": "executed-from-attacker-origin",
    "marker_written": true,
    "server_started": true,
    "tool_call_content": [
      "recorded:executed-from-attacker-origin"
    ],
    "tool_call_error": false,
    "tool_names": [
      "record_marker"
    ],
    "vulnerable": true
  },
  "vulnerable": true
}
```

