GHSA-x227-pf99-vffg: PraisonAI: MCP SSE transport binds 0.0.0.0 with no authentication and no Origin validation; bundled SecurityConfig is never wired in
The MCP SSE server started via ToolsMCPServer.run_sse() / launch_tools_mcp_server(transport="sse")
binds to 0.0.0.0 by default and builds its Starlette application with no authentication middleware
and no Origin-header validation. The module mcp/mcp_security.py provides exactly the needed controls
(origin validation, DNS-rebinding detection, auth-header enforcement, a SecurityConfig), but none of
these functions are ever called by any transport — they are dead code. Any host that can reach the
port can list and invoke every registered tool with no credentials, and a victim's browser can drive
the same calls against a localhost instance via DNS rebinding.Affected code: src/praisonai-agents/praisonaiagents/mcp/mcp_server.pyrun_sse defaults host to all interfaces (line 245) and builds the app with only `debug` and `routes`no `middleware=` and no per-route auth/origin gate (lines ~271-289):
  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)launch_tools_mcp_server also defaults host="0.0.0.0" (line 301).src/praisonai-agents/praisonaiagents/mcp/mcp_security.py defines but the transports never call:is_valid_origin (line 30), is_potential_dns_rebinding (line 110), validate_auth_header (line 167),
SecurityConfig.is_origin_allowed (line 236). These symbols are referenced only inside mcp_security.py
and the __init__ re-export. (mcp_websocket.py's auth references are CLIENT-side, not server validation.)Impact:
launch_tools_mcp_server(transport="sse") is the documented path for exposing tools over MCP. With the
defaults above it is an unauthenticated, network-reachable tool-execution endpoint. Blast radius equals
the capabilities of the registered tools; with file/shell/code-exec tools this is RCE. With no Origin
check, a malicious page the victim merely visits can rebind its hostname to 127.0.0.1 and issue the
JSON-RPC calls cross-origin against a developer's local server.Proof of concept:
Static proof (AST analysis of unmodified source):
  Check 1 - run_sse(host='0.0.0.0'); launch_tools_mcp_server(host='0.0.0.0')  -> EXPOSED
  Check 2 - Starlette(...) kwargs: ['debug','routes']  -> NO middleware= (no auth/origin gate)
  Check 3 - is_valid_origin / is_potential_dns_rebinding / validate_auth_header / SecurityConfig
            never called by any transport  -> DEAD CODE
Live exploitation against a running server:
  curl -N http://VICTIM:8080/sseevent: 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":{...}}}'
No Authorization header anywhere. Browser DNS-rebinding variant drives the same calls cross-origin.Remediation:
Wire in the existing mcp_security.py controls and fix defaults:Default run_sse(host="127.0.0.1"); require explicit opt-in to bind 0.0.0.0.Attach Starlette middleware calling is_valid_origin / is_potential_dns_rebinding; reject bad origins.Enforce validate_auth_header when SecurityConfig.require_auth; default require_auth=True (and
allow_missing_origin=False) for any non-loopback bind.Distinct from prior advisories:
The accepted MCP advisories are tool-handler bugs — tools/call path traversal -> .pth RCE
(GHSA-9mqq-jqxf-grvw) and unauthenticated file read via workflow.show/validate (GHSA-9cr9-25q5-8prj).
This is a transport-layer missing-auth/exposure: the SSE server never enforces auth or Origin validation
and ignores the security module the codebase ships. Closest in spirit to the default-insecure pattern
(GHSA-8444 / 86qc) but a different server and a different root cause (unwired controls, not an unset env var).
CVSS v3.1: 9.8