GitHub Security Advisory: GHSA-r9x3-wx45-2v7f
Package: PraisonAI (PIP)
Severity: HIGH (CVSS 7.1)
CWE: CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Vulnerable versions: <= 4.5.112
Patched in: 4.5.113

Summary: PraisonAI recipe registry publish path traversal allows out-of-root file write

Details:
### Summary

PraisonAI's recipe registry publish endpoint writes uploaded recipe bundles to a filesystem path derived from the bundle's internal `manifest.json` before it verifies that the manifest `name` and `version` match the HTTP route. A malicious publisher can place `../` traversal sequences in the bundle manifest and cause the registry server to create files outside the configured registry root even though the request is ultimately rejected with HTTP `400`.

This is an arbitrary file write / path traversal issue on the registry host. It affects deployments that expose the recipe registry publish flow. If the registry is intentionally run without a token, any network client that can reach the service can trigger it. If a token is configured, any user with publish access can still exploit it.

### Details

The bug is caused by the order of operations between the HTTP handler and the registry storage layer.

1. `RegistryServer._handle_publish()` in `src/praisonai/praisonai/recipe/server.py:370-426` parses `POST /v1/recipes/{name}/{version}`, writes the uploaded `.praison` file to a temporary path, and immediately calls:

```python
result = self.registry.publish(tmp_path, force=force)
```

2. `LocalRegistry.publish()` in `src/praisonai/praisonai/recipe/registry.py:214-287` opens the uploaded tarball, reads `manifest.json`, and trusts the attacker-controlled `name` and `version` fields:

```python
name = manifest.get("name")
version = manifest.get("version")
recipe_dir = self.recipes_path / name / version
recipe_dir.mkdir(parents=True, exist_ok=True)
bundle_name = f"{name}-{version}.praison"
dest_path = recipe_dir / bundle_name
shutil.copy2(bundle_path, dest_path)
```

3. Validation helpers already exist in the same file:

```python
def _validate_name(name: str) -> bool:
def _validate_version(version: str) -> bool:
```

but they are not called before the filesystem write.

4. Only after `publish()` returns does the route compare the manifest values with the URL values:

```python
if result["name"] != name or result["version"] != version:
    self.registry.delete(result["name"], result["version"])
    return self._error_response(...)
```

At that point the out-of-root artifact has already been created. The request returns an error, but the write outside the registry root remains on disk.

Verified vulnerable behavior:

- Request path: `/v1/recipes/safe/1.0.0`
- Internal manifest name: `../../outside-dir`
- Server response: HTTP `400`
- Leftover artifact: `/tmp/praisonai-publish-traversal-poc/outside-dir-1.0.0.praison`

This demonstrates that the write occurs before the consistency check and rollback.

### PoC

Run the single verification script from the checked-out repository:

```bash
cd "/Users/r1zzg0d/Documents/CVE hunting/targets/PraisonAI"
python3 tmp/pocs/poc.py
```

Expected vulnerable output:

```text
[+] Publish response status: 400
{
  "ok": false,
  "error": "Bundle name/version (../../outside-dir@1.0.0) doesn't match URL (safe@1.0.0)