GHSA-r9x3-wx45-2v7f
PraisonAI recipe registry publish path traversal allows out-of-root file write
Packages
Details
As written in the advisory (Markdown source, shown as plain text).
### 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 (../../[email protected]) doesn't match URL ([email protected])",
"code": "error"
}
[+] Leftover artifact exists: True
[+] Artifact under registry root: False
[+] RESULT: VULNERABLE - upload was rejected, but an out-of-root artifact was still created.
```
Then verify the artifact manually:
```bash
ls -l /tmp/praisonai-publish-traversal-poc/outside-dir-1.0.0.praison
find /tmp/praisonai-publish-traversal-poc -maxdepth 2 | sort
```
What the script does internally:
1. Starts a local PraisonAI recipe registry server.
2. Builds a malicious `.praison` bundle whose internal `manifest.json` contains `name = ../../outside-dir`.
3. Uploads that bundle to the apparently safe route `/v1/recipes/safe/1.0.0`.
4. Receives the expected `400` mismatch error.
5. Confirms that `outside-dir-1.0.0.praison` was still written outside the configured registry directory.
### Impact
This is a path traversal / arbitrary file write vulnerability in the recipe registry publish flow.
Impacted parties:
- Registry operators running the PraisonAI recipe registry service.
- Any deployment that allows remote recipe publication.
- Any environment where adjacent writable filesystem locations contain sensitive application data, service files, or staged content that could be overwritten or planted.
Security impact:
- Integrity impact is high because an attacker can create or overwrite files outside the registry root.
- Availability impact is possible if the attacker targets adjacent runtime or application files.
- The issue can be chained with other local loading or deployment behaviors if nearby files are later consumed by another component.
### Remediation
1. Validate `manifest.json` `name` and `version` before any path join or filesystem write. Reject path separators, `..`, absolute paths, and any value that fails the existing `_validate_name()` / `_validate_version()` checks.
2. Resolve the final destination path and enforce that it remains under the configured registry root before calling `mkdir()` or `copy2()`. For example, compare the resolved destination against `self.recipes_path.resolve()`.
3. Move the URL-to-manifest consistency check ahead of `self.registry.publish(...)`, or refactor `publish()` so it receives already-validated route parameters instead of trusting attacker-controlled manifest values for storage paths.
References
External links from the advisory. They lead to third-party sites.