PraisonAI’s recipe registry contains a critical path traversal vulnerability in its publish endpoint. Attackers with publish access can write arbitrary files outside the designated registry root directory. The server rejects the request with a 400 error after the write, but the file persists on disk. This affects any deployment exposing the /v1/recipes/{name}/{version} POST endpoint.
The flaw stems from flawed operation ordering in the server code. When a client uploads a .praison bundle—a tarball containing a manifest.json—the handler extracts the manifest’s name and version fields without validation. It constructs the target path using those values, creates directories if needed, and copies the bundle there. Only afterward does it check if the manifest matches the URL parameters. If not, it attempts deletion—but only of the intended path, leaving the traversal-written file intact.
Technical Breakdown
Examine the code in src/praisonai/praisonai/recipe/server.py and registry.py. The _handle_publish method parses the POST request, saves the bundle to a temp file, and calls registry.publish(tmp_path):
result = self.registry.publish(tmp_path, force=force)
Inside LocalRegistry.publish, it reads manifest.json blindly:
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)
Validation functions like _validate_name and _validate_version exist but run too late, post-write. Post-publish, the handler checks:
if result["name"] != name or result["version"] != version:
self.registry.delete(result["name"], result["version"])
return self._error_response(...)
The delete targets the URL-derived path, not the manifest one. A PoC confirms this: POST to /v1/recipes/safe/1.0.0 with manifest name “../../outside-dir” and version “1.0.0” yields 400 but creates /tmp/praisonai-publish-traversal-poc/outside-dir-1.0.0.praison.
Run the PoC script for verification:
cd "/path/to/PraisonAI"
python3 tmp/pocs/poc.py
Output shows the 400 response and confirms the leftover file:
[+] Publish response status: 400
{
"ok": false,
"error": "Bundle name/version (../../outside-dir@1.0.0) doesn't match URL (safe@1.0.0)",
"code": "error"
}
[+] Leftover artifact detected
Impact and Why It Matters
PraisonAI is an open-source framework for AI agents and workflows. Recipes are reusable bundles shared via a registry, akin to npm packages or Hugging Face models. This vuln exposes registry servers—often self-hosted in development or production—to supply-chain risks.
If no auth token configures (default for local runs), any network-accessible client exploits it. With tokens, any authorized publisher can. Implications scale with server context:
- Arbitrary writes: Overwrite configs, scripts, or logs. Target /etc/passwd or web roots for escalation.
- DoS: Fill disks in /tmp or roots with junk dirs/files.
- RCE potential: Write CGI scripts or cron jobs if perms allow. AI registries run as services, often root or www-data.
- Supply-chain: Plant backdoors in legit-looking recipes for downstream pulls.
Real-world parallels: PyPI’s infinite RCES via path traversal (2023), npm’s flat vectors. PraisonAI’s GitHub shows <1k stars (as of late 2024), niche but growing in AI tooling. Users deploy registries casually, assuming local safety. This assumes filesystem trust—wrong in shared/multi-tenant setups.
Mitigate now: Disable public publish, run registries chrooted/jailed, or patch validation pre-write. Upstream fix: Call validators before mkdir/copy, use safe paths. Monitor for CVEs; reporter eyes CVE assignment.
Bottom line: Exposes a classic mistake—trusting user input pre-write. In AI’s rush, secure the pipelines first. Check your PraisonAI deploys; a quick PoC run reveals exposure.