fix: enforce workdir confinement in LocalFileManagerDriver to prevent path traversal - #2195
Conversation
… path traversal _full_path() bypassed the workdir for absolute paths (os.path.isabs check returned the path unchanged) and had no boundary check after normpath resolved '..' sequences. Both vectors allowed reading/writing arbitrary files on the host filesystem. This change: - Removes the isabs short-circuit; every path is joined with workdir (stripping a leading '/' so os.path.join doesn't discard the base). - Adds a realpath-based boundary check: if the resolved path falls outside workdir_real, a ValueError is raised before any I/O is attempted. Signed-off-by: FailSafe Researcher <joshua@getfailsafe.com>
|
Hi maintainers 👋 This vulnerability was found by FailSafe — a top agentic cybersecurity company specializing in automated deep security analysis of AI/ML and agentic codebases. We're reporting these initial findings as a social good contribution to help secure the open-source AI ecosystem. If you'd like us to perform a deeper, more comprehensive security scan of your project, we'd love to hear from you — reach out at joshua@getfailsafe.com. Thanks for maintaining this project! 🙏 |
|
This security fix PR has been open for 3+ weeks without review. We understand maintainers are busy. If you prefer, we can also reach out via security@ email. Happy to rebase or adjust the patch. Please let us know how you'd like to proceed. |
|
Friendly follow-up on this security PR. It has been open for a while without human reviewer feedback. Happy to rebase, trim the patch, or adjust the approach if that helps. Thanks for taking a look. |
Problem
LocalFileManagerDriver._full_path()(griptape/drivers/file_manager/local_file_manager_driver.py:52) contains two path traversal vectors that allow an AI agent to read or write arbitrary files outside the configuredworkdir:Vector 1 — Absolute path bypass:
If
path = "/etc/passwd",os.path.isabs()returns True andfull_path = "/etc/passwd"— the workdir is completely skipped.Vector 2 —
..traversal via normpath:../../../etc/passwdis not absolute, so it joins with workdir:/workdir/../../../etc/passwd.os.path.normpath()then resolves..sequences, producing/etc/passwd— outside the workdir with no boundary check.Exploit scenario: An adversarial document processed by a griptape agent can use prompt injection to instruct the LLM to call
FileManagerTool.load_files_from_diskwith a traversal path, leaking SSH private keys,.envfiles, AWS credentials, or other sensitive files.Confirmed by test:
Severity: High (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N — 8.7)
Fix
Remove the
isabsshort-circuit so all paths go throughos.path.join(workdir, path.lstrip('/')), then add arealpath-based boundary check:Test Plan
_full_path("/etc/passwd")raisesValueError(was: returned/etc/passwd)_full_path("../../../etc/passwd")raisesValueError(was: returned/etc/passwd)_full_path("uploads/file.txt")returns<workdir>/uploads/file.txt(no regression)_full_path("subdir/")returns<workdir>/subdir/with trailing slash preservedSecurity Note
Severity: High. Exploitable via prompt injection in any griptape agent that uses
FileManagerToolwithLocalFileManagerDriver(the default). Filed via GitHub Private Vulnerability Reporting.