CVE-2026-40115: PraisonAI: unbounded body read enables local DoS

GHSA-2xgv-5cv2-47vv MEDIUM
Published April 10, 2026
CISO Take

PraisonAI's WSGI-based recipe registry server reads the entire HTTP request body into memory based on the client-supplied Content-Length header with no upper bound, and authentication is disabled by default unless explicitly configured via PRAISONAI_REGISTRY_TOKEN — meaning any local process can crash the server by uploading arbitrarily large bundles. While the CVSS 6.2 score and local-only attack vector (AV:L) limit direct internet exposure, shared environments, containers, and SSRF-vulnerable co-located services all meaningfully expand the realistic attack surface, and the package carries 31 other CVEs indicating a pattern of security debt worth factoring into your AI supply chain risk posture. A single ~500MB multipart POST exhausts gigabytes of RAM and causes OOM-kill on the registry process; repeated requests also silently fill disk at ~/.praison/registry/ with no quota enforcement. Patch to v4.5.128 immediately and set PRAISONAI_REGISTRY_TOKEN — the no-auth default should never be left in place on any shared host.

Sources: NVD GitHub Advisory ATLAS

Risk Assessment

Medium risk (CVSS 6.2, AV:L/AC:L/PR:N/UI:N). The local attack vector significantly constrains exploitability — an attacker must have local process access or leverage SSRF from another co-located service. No authentication is required by default, removing any credential barrier for local attackers. Impact is limited to availability (A:H) with no confidentiality or integrity implications. In containerized or multi-tenant AI pipeline environments, the local process boundary is less meaningful and risk increases. Absence of EPSS data and CISA KEV listing suggests no observed in-the-wild exploitation, but the PoC is trivially simple (two curl commands) and the vulnerability requires zero AI/ML knowledge to exploit.

Attack Kill Chain

Local Access
Attacker gains local process access to the host running PraisonAI registry via SSH, shared container environment, or SSRF from a co-located service (e.g., vulnerable web agent on same host).
AML.T0049
Authentication Bypass
Attacker exploits the default no-authentication configuration — PRAISONAI_REGISTRY_TOKEN is not set — and interacts freely with the registry endpoint at 127.0.0.1:7777 without any credentials.
Resource Exhaustion
Attacker sends large multipart POST requests (e.g., 500MB bundles) to the recipe upload endpoint; the WSGI server reads the full body into memory with no size check, creating multiple in-memory copies totaling 1–1.5GB per request.
AML.T0034.001
Denial of Service
Memory exhaustion triggers OOM-kill on the registry process, halting all PraisonAI recipe publish and download operations and disrupting dependent AI agent workflows; repeated uploads also fill disk at ~/.praison/registry/.
AML.T0029

Affected Systems

Package Ecosystem Vulnerable Range Patched
PraisonAI pip < 4.5.128 4.5.128

Do you use PraisonAI? You're affected.

Severity & Risk

CVSS 3.1
6.2 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

Attack Surface

AV AC PR UI S C I A
AV Local
AC Low
PR None
UI None
S Unchanged
C None
I None
A High

Recommended Action

  1. Patch: Upgrade to praisonai >= 4.5.128 which adds a 10MB request size limit to the WSGI server, matching serve.py's existing RequestSizeLimitMiddleware protection.
  2. Authentication: Set PRAISONAI_REGISTRY_TOKEN environment variable or pass --token to praisonai registry serve; the default no-auth configuration must never be used in any shared or production environment.
  3. Network binding: Verify the registry binds only to 127.0.0.1 (default) and is not exposed on 0.0.0.0 in container or VM deployments — audit docker-compose and systemd unit files.
  4. Defense-in-depth: Apply process-level memory limits (systemd MemoryMax, Docker --memory flag) to contain blast radius if exploited.
  5. If immediate patching is blocked, place a reverse proxy (nginx/caddy) in front of the registry enforcing client_max_body_size at 10MB.
  6. Disk quota: Monitor ~/.praison/registry/ disk usage; consider filesystem quotas on the user home directory.

Classification

Compliance Impact

This CVE is relevant to:

EU AI Act
Article 9 - Risk Management System
ISO 42001
8.4 - AI system operation
NIST AI RMF
MANAGE 2.2 - AI risk response mechanisms
OWASP LLM Top 10
LLM04 - Model Denial of Service

Frequently Asked Questions

What is CVE-2026-40115?

PraisonAI's WSGI-based recipe registry server reads the entire HTTP request body into memory based on the client-supplied Content-Length header with no upper bound, and authentication is disabled by default unless explicitly configured via PRAISONAI_REGISTRY_TOKEN — meaning any local process can crash the server by uploading arbitrarily large bundles. While the CVSS 6.2 score and local-only attack vector (AV:L) limit direct internet exposure, shared environments, containers, and SSRF-vulnerable co-located services all meaningfully expand the realistic attack surface, and the package carries 31 other CVEs indicating a pattern of security debt worth factoring into your AI supply chain risk posture. A single ~500MB multipart POST exhausts gigabytes of RAM and causes OOM-kill on the registry process; repeated requests also silently fill disk at ~/.praison/registry/ with no quota enforcement. Patch to v4.5.128 immediately and set PRAISONAI_REGISTRY_TOKEN — the no-auth default should never be left in place on any shared host.

Is CVE-2026-40115 actively exploited?

No confirmed active exploitation of CVE-2026-40115 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-40115?

1. Patch: Upgrade to praisonai >= 4.5.128 which adds a 10MB request size limit to the WSGI server, matching serve.py's existing RequestSizeLimitMiddleware protection. 2. Authentication: Set PRAISONAI_REGISTRY_TOKEN environment variable or pass --token to praisonai registry serve; the default no-auth configuration must never be used in any shared or production environment. 3. Network binding: Verify the registry binds only to 127.0.0.1 (default) and is not exposed on 0.0.0.0 in container or VM deployments — audit docker-compose and systemd unit files. 4. Defense-in-depth: Apply process-level memory limits (systemd MemoryMax, Docker --memory flag) to contain blast radius if exploited. 5. If immediate patching is blocked, place a reverse proxy (nginx/caddy) in front of the registry enforcing client_max_body_size at 10MB. 6. Disk quota: Monitor ~/.praison/registry/ disk usage; consider filesystem quotas on the user home directory.

What systems are affected by CVE-2026-40115?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, AI orchestration pipelines, model serving.

What is the CVSS score for CVE-2026-40115?

CVE-2026-40115 has a CVSS v3.1 base score of 6.2 (MEDIUM).

Technical Details

NVD Description

## Summary The WSGI-based recipe registry server (`server.py`) reads the entire HTTP request body into memory based on the client-supplied `Content-Length` header with no upper bound. Combined with authentication being disabled by default (no token configured), any local process can send arbitrarily large POST requests to exhaust server memory and cause a denial of service. The Starlette-based server (`serve.py`) has `RequestSizeLimitMiddleware` with a 10MB limit, but the WSGI server lacks any equivalent protection. ## Details The vulnerable code path in `src/praisonai/praisonai/recipe/server.py`: **1. No size limit on body read (line 551-555):** ```python content_length = int(environ.get("CONTENT_LENGTH", 0)) body = environ["wsgi.input"].read(content_length) if content_length > 0 else b"" ``` The `content_length` is taken directly from the HTTP header with no maximum check. The entire body is read into a single `bytes` object in memory. **2. Second in-memory copy via multipart parsing (line 169-172):** ```python result = {"fields": {}, "files": {}} boundary_bytes = f"--{boundary}".encode() parts = body.split(boundary_bytes) ``` The `_parse_multipart` method splits the already-buffered body and stores file contents in a dict, creating additional in-memory copies. **3. Third copy to temp file (line 420-421):** ```python with tempfile.NamedTemporaryFile(suffix=".praison", delete=False) as tmp: tmp.write(bundle_content) ``` The bundle content is then written to disk and persisted in the registry, also without size checks. **4. Authentication disabled by default (line 91-94):** ```python def _check_auth(self, headers: Dict[str, str]) -> bool: if not self.token: return True # No token configured = no auth ``` The `self.token` defaults to `None` unless `PRAISONAI_REGISTRY_TOKEN` is set or `--token` is passed on the CLI. The entry point is `praisonai registry serve` (cli/features/registry.py:176), which calls `run_server()` binding to `127.0.0.1:7777` by default. In contrast, `serve.py` (the Starlette server) has `RequestSizeLimitMiddleware` at line 725-732 enforcing a 10MB default limit. The WSGI server has no equivalent. ## PoC ```bash # Start the registry server with default settings (no auth, localhost) praisonai registry serve & # Step 1: Create a large bundle (~500MB) mkdir -p /tmp/dos-test echo '{"name":"dos","version":"1.0.0"}' > /tmp/dos-test/manifest.json dd if=/dev/zero of=/tmp/dos-test/pad bs=1M count=500 tar czf /tmp/dos-bundle.praison -C /tmp/dos-test . # Step 2: Upload — server buffers ~500MB into RAM with no limit curl -X POST http://127.0.0.1:7777/v1/recipes/dos/1.0.0 \ -F 'bundle=@/tmp/dos-bundle.praison' -F 'force=true' # Step 3: Repeat to exhaust memory for v in 1.0.{1..10}; do curl -X POST http://127.0.0.1:7777/v1/recipes/dos/$v \ -F 'bundle=@/tmp/dos-bundle.praison' & done # Server process will be OOM-killed ``` ## Impact - **Memory exhaustion**: A single large request can consume all available memory, crashing the server process (and potentially other processes via OOM killer). - **Disk exhaustion**: Repeated uploads persist bundles to disk at `~/.praison/registry/` with no quota, potentially filling the filesystem. - **No authentication barrier**: Default configuration requires no token, so any local process (including via SSRF from other services on the same host) can trigger this. - **Availability impact**: The registry server becomes unavailable, blocking recipe publish/download operations. The default bind address of `127.0.0.1` limits exploitability to local attackers or SSRF scenarios. If a user binds to `0.0.0.0` (common for shared environments or containers), the attack surface extends to the network. ## Recommended Fix Add a request size limit to the WSGI application, consistent with `serve.py`'s 10MB default: ```python # In create_wsgi_app(), before reading the body: MAX_REQUEST_SIZE = 10 * 1024 * 1024 # 10MB, matching serve.py def application(environ, start_response): # ... existing code ... # Read body with size limit try: content_length = int(environ.get("CONTENT_LENGTH", 0)) except (ValueError, TypeError): content_length = 0 if content_length > MAX_REQUEST_SIZE: status = "413 Request Entity Too Large" response_headers = [("Content-Type", "application/json")] body = json.dumps({ "error": { "code": "request_too_large", "message": f"Request body too large. Max: {MAX_REQUEST_SIZE} bytes" } }).encode() start_response(status, response_headers) return [body] body = environ["wsgi.input"].read(content_length) if content_length > 0 else b"" # ... rest of handler ... ``` Additionally, consider: - Adding a `--max-request-size` CLI flag to `praisonai registry serve` - Adding per-recipe disk quota enforcement in `LocalRegistry.publish()`

Exploitation Scenario

An attacker with local shell access (or exploiting SSRF from another co-located service such as a vulnerable LangChain web agent or Gradio app on the same host) targets the PraisonAI registry running on 127.0.0.1:7777 with default settings. Since no authentication token is configured, they craft a 500MB multipart POST to the recipe upload endpoint using a single curl command from the PoC. The WSGI server reads the entire body into RAM, then splits it again during multipart parsing, and writes it to a temp file — consuming 1–1.5GB per request. Sending 5–10 concurrent requests in a loop triggers OOM-kill on the registry process and potentially other services sharing the host's memory. In a CI/CD pipeline running PraisonAI agent workflows inside a shared container, this halts all recipe distribution and agent task orchestration, causing a full pipeline availability failure.

CVSS Vector

CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Timeline

Published
April 10, 2026
Last Modified
April 10, 2026
First Seen
April 10, 2026

Related Vulnerabilities