{"id":"GHSA-x227-pf99-vffg","title":"PraisonAI: MCP SSE transport binds 0.0.0.0 with no authentication and no Origin validation; bundled SecurityConfig is never wired in","summary":"PraisonAI: MCP SSE transport binds 0.0.0.0 with no authentication and no Origin validation; bundled SecurityConfig is never wired in","severity":"critical","cvss":9.8,"cwe":["CWE-306","CWE-350","CWE-1327"],"vendor":"praisonaiagents","product":"praisonaiagents","ecosystem":"pip","affected":["praisonaiagents < 1.6.59"],"patched":["praisonaiagents 1.6.59"],"published":"2026-06-18","updated":"2026-06-18","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-x227-pf99-vffg","references":[{"url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-x227-pf99-vffg"},{"url":"https://github.com/advisories/GHSA-x227-pf99-vffg"}],"tags":["ghsa","pip"],"ingestedAt":"2026-06-29T14:31:46.970Z","slug":"GHSA-x227-pf99-vffg","body":"## Overview\n\nThe MCP SSE server started via ToolsMCPServer.run_sse() / launch_tools_mcp_server(transport=\"sse\")\nbinds to 0.0.0.0 by default and builds its Starlette application with no authentication middleware\nand no Origin-header validation. The module mcp/mcp_security.py provides exactly the needed controls\n(origin validation, DNS-rebinding detection, auth-header enforcement, a SecurityConfig), but none of\nthese functions are ever called by any transport — they are dead code. Any host that can reach the\nport can list and invoke every registered tool with no credentials, and a victim's browser can drive\nthe same calls against a localhost instance via DNS rebinding.\n\nAffected code: src/praisonai-agents/praisonaiagents/mcp/mcp_server.py\n- run_sse defaults host to all interfaces (line 245) and builds the app with only `debug` and `routes`\n  - no `middleware=` and no per-route auth/origin gate (lines ~271-289):\n      app = Starlette(debug=self._debug, routes=[\n          Route(sse_path, endpoint=handle_sse),                          # \"/sse\"\n          Mount(messages_path, app=sse_transport.handle_post_message),   # \"/messages/\"\n      ])\n      uvicorn.run(app, host=host, port=port)\n- launch_tools_mcp_server also defaults host=\"0.0.0.0\" (line 301).\n\nsrc/praisonai-agents/praisonaiagents/mcp/mcp_security.py defines but the transports never call:\n- is_valid_origin (line 30), is_potential_dns_rebinding (line 110), validate_auth_header (line 167),\n  SecurityConfig.is_origin_allowed (line 236). These symbols are referenced only inside mcp_security.py\n  and the __init__ re-export. (mcp_websocket.py's auth references are CLIENT-side, not server validation.)\n\nImpact:\nlaunch_tools_mcp_server(transport=\"sse\") is the documented path for exposing tools over MCP. With the\ndefaults above it is an unauthenticated, network-reachable tool-execution endpoint. Blast radius equals\nthe capabilities of the registered tools; with file/shell/code-exec tools this is RCE. With no Origin\ncheck, a malicious page the victim merely visits can rebind its hostname to 127.0.0.1 and issue the\nJSON-RPC calls cross-origin against a developer's local server.\n\nProof of concept:\nStatic proof (AST analysis of unmodified source):\n  Check 1 - run_sse(host='0.0.0.0'); launch_tools_mcp_server(host='0.0.0.0')  -> EXPOSED\n  Check 2 - Starlette(...) kwargs: ['debug','routes']  -> NO middleware= (no auth/origin gate)\n  Check 3 - is_valid_origin / is_potential_dns_rebinding / validate_auth_header / SecurityConfig\n            never called by any transport  -> DEAD CODE\nLive exploitation against a running server:\n  curl -N http://VICTIM:8080/sse\n  #   event: endpoint  / data: /messages/?session_id=<sid>\n  curl -X POST \"http://VICTIM:8080/messages/?session_id=<sid>\" -H 'Content-Type: application/json' \\\n    -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\n         \"capabilities\":{},\"clientInfo\":{\"name\":\"x\",\"version\":\"1\"}}}'\n  curl -X POST \"http://VICTIM:8080/messages/?session_id=<sid>\" -H 'Content-Type: application/json' \\\n    -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"<tool>\",\"arguments\":{...}}}'\nNo Authorization header anywhere. Browser DNS-rebinding variant drives the same calls cross-origin.\n\nRemediation:\nWire in the existing mcp_security.py controls and fix defaults:\n- Default run_sse(host=\"127.0.0.1\"); require explicit opt-in to bind 0.0.0.0.\n- Attach Starlette middleware calling is_valid_origin / is_potential_dns_rebinding; reject bad origins.\n- Enforce validate_auth_header when SecurityConfig.require_auth; default require_auth=True (and\n  allow_missing_origin=False) for any non-loopback bind.\n\nDistinct from prior advisories:\nThe accepted MCP advisories are tool-handler bugs — tools/call path traversal -> .pth RCE\n(GHSA-9mqq-jqxf-grvw) and unauthenticated file read via workflow.show/validate (GHSA-9cr9-25q5-8prj).\nThis is a transport-layer missing-auth/exposure: the SSE server never enforces auth or Origin validation\nand ignores the security module the codebase ships. Closest in spirit to the default-insecure pattern\n(GHSA-8444 / 86qc) but a different server and a different root cause (unwired controls, not an unset env var).\n\n## Affected packages\n\n- `praisonaiagents < 1.6.59`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `praisonaiagents 1.6.59`","depth":"midnight","depthScore":54,"depthScoreParts":{"impact":53.9,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}