May 11, 2026·7 min read·3 visits
A symlink-based path traversal in PraisonAI's recipe unpacking allows arbitrary file overwriting, potentially leading to remote code execution.
PraisonAI versions prior to 4.6.37 contain a path traversal vulnerability in the `_safe_extractall` function. The flaw allows an attacker to write arbitrary files outside the intended extraction directory via maliciously crafted tar archives containing unresolved symbolic links.
PraisonAI is a multi-agent systems platform that relies on a recipe ecosystem to share and deploy configurations. The platform exposes recipe pull, recipe publish, and recipe unpack workflows to manage these configurations via archive files. The vulnerability resides within the _safe_extractall helper function located in src/praisonai/praisonai/recipe/registry.py.
The _safe_extractall function is responsible for securely extracting tar archives to a designated destination directory. While the function implements checks to prevent standard directory traversal using absolute paths and .. segments, it fails to account for symbolic and hard links. This class of vulnerability is categorized as CWE-59: Improper Link Resolution Before File Access, commonly referred to as a "TarSlip" vulnerability.
An attacker exploiting this flaw can construct a malicious archive that bypasses the directory restrictions during the extraction process. When a victim processes the malicious recipe archive, the application writes files to arbitrary locations on the victim's filesystem. This arbitrary file write capability directly enables privilege escalation and remote code execution by targeting sensitive configuration files or execution paths.
The root cause of the vulnerability is a discrepancy between the static validation of archive members and the stateful extraction behavior of the Python tarfile library. The _safe_extractall function iterates over the tar.getmembers() list and performs lexical validation on the member.name attribute. It uses Path.resolve() to ensure the evaluated destination path falls within the target directory boundaries.
This validation approach is inadequate because a Tar archive can contain members defined as symbolic links (SYMTYPE) or hard links (LNKTYPE). These specific member types contain a linkname attribute that specifies the target of the link. The original implementation of _safe_extractall entirely omitted validation of the linkname attribute.
Because the validation loop executes prior to the extraction of any files, Path.resolve() evaluates the paths lexically against the current filesystem state. A path referencing a symlink that does not yet exist on the filesystem will resolve successfully as a standard path within the destination directory. The validation logic approves the member, unaware that the path will behave as a symlink during actual extraction.
The extraction phase occurs via a single call to tar.extractall(). This function processes members sequentially in the exact order they appear in the archive. If an attacker places a symlink member first, extractall() creates the symlink on the disk. When a subsequent member targets a path inside that symlink, the operating system routes the write operation through the symlink to the external directory.
The original implementation of _safe_extractall applied structural checks exclusively to the member.name property. It verified that paths were not absolute and did not contain directory traversal sequences. However, it passed the entire archive to tarfile.extractall(dest_dir) without assessing the internal symlink destinations.
The remediation introduced in commit 0cec9fd1c3fc457c70712d97e21ea1caaa32ecda implements strict resolution checks for the linkname attribute. The patch explicitly queries member.issym() and member.islnk() during the validation loop. If a link is detected, the application reconstructs the intended target relative to the member's extraction path.
if member.issym() or member.islnk():
linkname = member.linkname or ""
if linkname.startswith("/"):
raise RegistryError(f"Refusing to extract link with absolute target: {member.name} -> {linkname}")
# Resolve the intended target relative to the member's position
link_target = (dest_resolved / member_path.parent / linkname).resolve()
# Ensure the resolved target is still within the destination directory
if not str(link_target).startswith(str(dest_resolved) + os.sep) and link_target != dest_resolved:
raise RegistryError(f"Refusing to extract link escaping target directory: {member.name} -> {linkname}")Beyond manual validation, the patch introduces defense-in-depth by adopting the PEP 706 security filter for the tarfile module. The execution phase now calls tar.extractall(dest_dir, filter="data") within a try-except TypeError block. On Python 3.12 and newer, the data filter natively blocks the extraction of absolute paths and symlinks that traverse outside the target directory.
Exploiting this vulnerability requires the construction of a specifically ordered GZipped Tar archive. The attacker must control the sequence of the archive members to manipulate the internal state of the extraction process. The first required component is a symlink member pointing to the target external directory, such as /home/user/.ssh/.
The second required component is a standard file member routed through the namespace of the previously defined symlink. The file name must be prefixed with the symlink's name, for example, symlink_name/authorized_keys. The attacker injects their payload, such as an SSH public key, into the contents of this file.
The exploit execution is triggered when the victim invokes praisonai recipe pull or praisonai recipe unpack on the malicious archive. The validation loop approves both members because their standard string representations lack explicit traversal characters. The extractall function subsequently creates the symlink and routes the file write operation to the external directory.
The vulnerability exerts a severe impact on the integrity of the host system. An attacker obtains arbitrary file write capabilities restricted only by the filesystem permissions of the user executing the PraisonAI utility. This allows the attacker to silently modify or overwrite any writable file on the system during routine recipe operations.
The standard progression for this vulnerability class involves immediate escalation to unauthenticated Remote Code Execution (RCE). Attackers typically achieve this by overwriting SSH .authorized_keys files, bash profile scripts (.bashrc), cron jobs, or the source code files of the PraisonAI application itself. The resulting compromise grants the attacker persistent access to the victim environment.
The vulnerability is tracked with a CVSS 4.0 score of 8.7 (High) and a CVSS 3.1 score of 7.5 (High). The metrics reflect the severity of the arbitrary write capability combined with the lack of required privileges, balanced against the necessity of user interaction to trigger the archive processing.
The calculated EPSS score is 0.00017 (0.045 percentile). The low probability of exploitation in the wild correlates with the requirement for user interaction via a CLI tool and the specific targeting required against the PraisonAI developer ecosystem.
The primary remediation strategy requires upgrading the PraisonAI application to version 4.6.37 or later. The patched release implements comprehensive structural validation for all symlink and hardlink targets prior to executing extraction operations. The fix is verified to properly reject archives containing outbound link definitions.
Organizations utilizing PraisonAI should mandate the use of Python 3.12 or newer across all execution environments. Python 3.12 introduces native protections against TarSlip vulnerabilities via the PEP 706 filter="data" parameter. The patched PraisonAI release integrates this native filter natively, establishing an enforced secondary boundary against unexpected extraction behaviors.
Administrators must enforce the principle of least privilege for the user accounts executing the PraisonAI utilities. Restricting the filesystem write permissions of the application process limits the blast radius of arbitrary file write vulnerabilities. The application process should explicitly lack write access to sensitive configuration directories, cron configurations, and system binaries.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
PraisonAI PraisonAI | < 4.6.37 | 4.6.37 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-59 (Link Following) / CWE-22 (Path Traversal) |
| Attack Vector | Network (Malicious Archive) |
| CVSS 4.0 Score | 8.7 |
| CVSS 3.1 Score | 7.5 |
| Impact | Arbitrary File Write / Remote Code Execution |
| EPSS Score | 0.00017 |
| Exploit Status | PoC Available |
Improper Link Resolution Before File Access ('Link Following')