Описание
PraisonAI: [Path Traversal] agent tools escape the configured workspace via symlinks
Summary
PraisonAI's praisonai.code tool wrappers (exported as CODE_TOOLS for agents) expose a workspace setting that the module itself treats as a path-traversal security boundary — read_file, write_file, apply_diff, and search_replace explicitly call is_path_within_directory() and return "… is outside the workspace" on violations. That boundary is enforced unsoundly and inconsistently:
- The containment helper uses
os.path.abspath(), notrealpath()/Path.resolve(). A symlink located inside the workspace whose target is outside has anabspath()that is still inside the workspace, so it passes the check whileopen()follows the link. This bypasses read, write, apply_diff, and search_replace (CWE-59). list_files()resolvespathagainst the workspace but never calls the containment helper at all —../and absolute paths escape directly (CWE-22).execute_command()takes aworkspaceargument documented "for security validation" but performs nocwdcontainment check;code_execute_command()resolves a relativecwdagainst the workspace and also never validates it (and never even passesworkspaceto the low-level helper). A relativecwd="../outside"runs commands from outside the workspace (CWE-22). An attacker who can influence an agent that has these tools attached (untrusted prompt, indirect prompt injection, or a server-exposed agent) can read, overwrite, list, and execute from outside the configured workspace, bounded only by the process user's filesystem permissions.
Technical Detail
1. Unsound containment helper (symlink bypass — CWE-59)
read_file/write_file/apply_diff/search_replace call this with the configured workspace (e.g. read_file.py: # Security check - ensure path is within workspace). Because abspath() does not canonicalize symlinks, a link at WORKSPACE/link_to_secret.txt → /outside/secret.txt has abspath WORKSPACE/link_to_secret.txt (inside) and passes, while open() follows it to the real outside target.
2. list_files() has no containment check (CWE-22)
3. execute_command() never validates cwd (CWE-22)
Note: execute_command rejects shell=True and runs shlex.split(command) via subprocess.run (no shell), so shell metacharacters (&&, >, pipes) do not work — but any binary still runs with attacker-chosen argv from the escaped cwd, which is sufficient to read/write outside the workspace.
The workspace is an intended boundary (pre-empts "by design")
The module asserts this control itself: read_file.py "Security check - ensure path is within workspace"; write_file.py "default workspace is cwd so relative paths cannot escape"; is_path_within_directory docstring "(prevents path traversal)"; execute_command workspace param "for security validation". The bug is that the asserted control is unsound (abspath vs realpath) and not applied to list_files/execute_command cwd.
Proof of Concept
Self-contained, local temp fixtures only; no network, no untrusted commands. Real praisonai.code agent tools were called.
Steps 2–6 each cross the configured workspace boundary; step 1 shows the plain-../ guard that the symlink and unscoped vectors bypass.
Impact
- Confidentiality: read files outside the workspace (in-workspace symlink; or list/enumerate outside dirs via
list_files). - Integrity: overwrite outside files via symlink; create/modify files outside the workspace via
execute_commandrunning in an escaped cwd. - Execution boundary: run arbitrary available binaries (argv-controlled) from a directory outside the workspace. Bounded by the process user's permissions. In a code-agent or server-exposed agent processing untrusted input, this exposes secrets / project-adjacent / host files and breaks the project-boundary integrity guarantee the workspace setting advertises.
Suggested Fix
- Replace
is_path_within_directory()with arealpath()/Path.resolve()-based containment check, and compare withos.path.commonpath()rather thanstartswith. - Apply that check consistently to every file path, directory path, backup path, diff/search-replace target, and command working directory, after full canonicalization (resolve the symlink's real target, not the link path).
list_files(): reject absolute paths and../escapes whenworkspaceis set.execute_command(): validatecwdcontainment whenworkspaceis set;code_execute_command()should pass_workspace_rootto the low-level helper or validate itself.- Regression tests: symlink read/write/diff/search-replace to outside targets;
list_files("../outside", workspace=…);execute_command(cwd="../outside", workspace=…); absolute outside paths with a workspace set.
Пакеты
PraisonAI
< 4.6.58
4.6.58
Связанные уязвимости
PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.51, is_path_within_directory() uses os.path.abspath() rather than os.path.realpath() for the workspace boundary. A symlink inside workspace can point outside and still pass the check, allowing read_file and other code tools to access files outside the configured workspace. This issue is fixed in version 4.6.58.