Skip to content

fix: enforce workdir confinement in LocalFileManagerDriver to prevent path traversal#2195

Open
Joshua-Medvinsky wants to merge 1 commit into
griptape-ai:mainfrom
Joshua-Medvinsky:fix/find-002-path-traversal-workdir-confinement
Open

fix: enforce workdir confinement in LocalFileManagerDriver to prevent path traversal#2195
Joshua-Medvinsky wants to merge 1 commit into
griptape-ai:mainfrom
Joshua-Medvinsky:fix/find-002-path-traversal-workdir-confinement

Conversation

@Joshua-Medvinsky

Copy link
Copy Markdown

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 configured workdir:

Vector 1 — Absolute path bypass:

full_path = path if os.path.isabs(path) else os.path.join(self.workdir, path.lstrip("/"))

If path = "/etc/passwd", os.path.isabs() returns True and full_path = "/etc/passwd" — the workdir is completely skipped.

Vector 2 — .. traversal via normpath:
../../../etc/passwd is 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_disk with a traversal path, leaking SSH private keys, .env files, AWS credentials, or other sensitive files.

Confirmed by test:

import os
workdir = '/tmp/safe'
for path in ['/etc/passwd', '../../../etc/passwd']:
    full = path if os.path.isabs(path) else os.path.join(workdir, path.lstrip('/'))
    full = os.path.normpath(full)
    print(f'{path!r}{full!r}')  # both resolve to /etc/passwd

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 isabs short-circuit so all paths go through os.path.join(workdir, path.lstrip('/')), then add a realpath-based boundary check:

def _full_path(self, path: str) -> str:
    full_path = os.path.join(self.workdir, path.lstrip("/"))
    ended_with_sep = path.endswith("/")
    full_path = os.path.normpath(full_path)
    workdir_real = os.path.realpath(self.workdir)
    full_path_real = os.path.realpath(full_path)
    if not (full_path_real == workdir_real or full_path_real.startswith(workdir_real + os.sep)):
        raise ValueError(f"Path {path!r} resolves outside the working directory {self.workdir!r}")
    if ended_with_sep:
        full_path = full_path.rstrip("/") + "/"
    return full_path

Test Plan

  • Existing test suite passes
  • _full_path("/etc/passwd") raises ValueError (was: returned /etc/passwd)
  • _full_path("../../../etc/passwd") raises ValueError (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 preserved

Security Note

Severity: High. Exploitable via prompt injection in any griptape agent that uses FileManagerTool with LocalFileManagerDriver (the default). Filed via GitHub Private Vulnerability Reporting.

… 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>
@failsafesecurity

Copy link
Copy Markdown

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! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants