CVE-2026-55531: PraisonAI: Unauthenticated unbounded session accumulation in the PraisonAI MCP HTTP server (memory exhaustion; session TTL never enforced)

Published Aug 25, 2026
·
Updated

Summary

The PraisonAI MCP HTTP-stream server creates a new in-memory session on every initialize request and never removes it. The cleanup routine that would expire sessions (cleanupsessions) is defined but never called anywhere in the codebase, and the configured session TTL is never enforced. There is no cap on the number of sessions. Because initialize requires no authentication and the server keeps every session dictionary forever, an attacker who can reach the endpoint (directly when the server is bound to a routable address, or from a victim's browser via the separate Origin-validation bypass) can drive memory usage up without bound until the process is killed by the out-of-memory killer. The same unbounded-growth pattern also applies to the cancelled-requests set populated by notifications/cancelled.

Details

In transports/httpstream.py, each initialize creates and stores a session with no limit:

python if body.get("method") == "initialize": newsessionid = str(uuid.uuid4()) self.sessions[newsessionid] = { "createdat": time.time(), "lastactivity": time.time(), }

A cleanup method exists:

python def cleanupsessions(self) -> None: now = time.time() expired = [sid for sid, data in self.sessions.items() if now - data["lastactivity"] > self.sessionttl] for sid in expired: del self.sessions[sid]

but grep across the package shows it has no call sites: it is never invoked on a timer, on request handling, or from any background task. self.sessionttl (default 3600) is stored and otherwise unused. There is no maximum-session check anywhere on the write path. As a result self.sessions grows monotonically for the lifetime of the process.

initialize is unauthenticated: in mcppost the API-key check is skipped when no key is configured (the default), and initialize does not require a prior session. The Origin check is the only gate, and a request with no Origin header is allowed; additionally the Origin allowlist is bypassable (see the companion report on the startswith Origin-validation bypass), so the endpoint is reachable from a malicious web page as well as directly.

The server-side cancellation set in server.py has the same defect:

python if method == "notifications/cancelled": requestid = params.get("requestId") if requestid: self.cancelledrequests.add(str(requestid)) # never cleared

self.cancelledrequests is an unbounded set that is added to but never pruned.

PoC

scripts/pocmcpsessiondos.sh. Start the server (default config, no API key):

praisonai mcp serve --transport http-stream --host 127.0.0.1 --port 8080

Send repeated initialize requests and watch the active session count grow:

bash for i in $(seq 1 200); do curl -s -o /dev/null -X POST http://127.0.0.1:8080/mcp \ -H 'Content-Type: application/json' -H 'Origin: http://localhost' \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}' done curl -s http://127.0.0.1:8080/health

Observed on 4.6.52 after 200 requests:

{"status":"healthy","server":"praisonai","version":"1.0.0","protocolversion":"2025-11-25","activesessions":200}

The count rises by one per request and never decreases; there is no TTL expiry and no cap. Sustained requests grow the process resident set without bound. Each session also retains any SSE event history keyed by session id, amplifying the per-session footprint.

Impact

An unauthenticated client can exhaust the memory of the host running the MCP server, leading to denial of service (the process is terminated by the OOM killer, taking down the agent endpoint). When the server is bound to a routable interface (for example --host 0.0.0.0, common in containers), this is a direct remote unauthenticated DoS. With the default localhost bind, it is reachable from any web page the operator visits, because initialize is unauthenticated and the Origin gate is bypassable. The defect is a missing cleanup wiring plus the absence of any session cap, so it manifests even under benign long-running use.

Remediation

Enforce the session TTL and cap the number of concurrent sessions: call cleanupsessions periodically (a background asyncio task, or opportunistically on each request) and reject new sessions with a 429/503 once a configurable maximum is reached. Bound cancelledrequests similarly (for example an LRU or a periodic prune keyed by age), since it is also never cleared. Require authentication by default on the HTTP-stream transport so that anonymous clients cannot create sessions at all.

Other sources

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, the MCP HTTP Stream mcppost handler creates a new sessions entry for every initialize request but does not call cleanupsessions or enforce a maximum. An unauthenticated caller can exhaust memory. The fix invokes cleanup and limits sessions through PRAISONAIMCPMAXSESSIONS. This issue is fixed in version 4.6.58.

MITRE

Affected Software

2 affected componentsFixes available
PraisonAI<4.6.58
pip/PraisonAI<4.6.58
4.6.58

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/PraisonAI to a version that resolves this vulnerability.

    Fixed in 4.6.58
  2. Upgrade

    Upgrade PraisonAI MCP HTTP-stream server to a version that resolves this vulnerability.

    Fixed in 4.6.58
  3. Configuration

    For the HTTP-stream transport, require authentication by default so anonymous clients cannot create MCP sessions via unauthenticated initialize requests.

    PraisonAI MCP HTTP-stream transport Require authentication by default = enabled
  4. Configuration

    Set and enforce a configurable maximum number of concurrent sessions using PRAISONAI_MCP_MAX_SESSIONS; reject new sessions with HTTP 429/503 once the maximum is reached.

    PraisonAI MCP HTTP-stream transport PRAISONAI_MCP_MAX_SESSIONS = (cap configured maximum)
  5. Configuration

    Enforce session TTL by wiring _cleanup_sessions to run periodically (background asyncio task or opportunistically on each request) so self._sessions entries expire after last_activity exceeds session_ttl.

    PraisonAI MCP HTTP-stream transport session TTL enforcement via _cleanup_sessions = enabled
  6. Configuration

    Apply cleanup/limits to _cancelled_requests (the set populated by notifications/cancelled) so it is pruned/cleared over time and/or capped to prevent unbounded growth.

    PraisonAI MCP HTTP-stream transport Cancellation request set pruning / cap = enabled

Event History

Aug 25, 2026
CVE Published
via MITRE·02:33 PM
Data Sourced
via MITRE·02:33 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·02:34 PM
Data Sourced
via GitHub·02:34 PM
DescriptionSeverityWeaknessAffected Software

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203