BUG-HUNT: [security] validate_path() in file_tools.py uses string prefix check allowing path escape to sibling directories #7336

Open
opened 2026-04-10 17:40:56 +00:00 by HAL9000 · 1 comment
Owner

Bug Report: Security — Unsafe Path Containment Check in file_tools.validate_path()

Severity Assessment

  • Impact: An attacker can craft a sandbox_root that is a prefix of a sibling directory. For example, if sandbox_root=/tmp/sandbox, a path resolving to /tmp/sandboxmalicious/file.txt passes the check because "/tmp/sandboxmalicious/file.txt".startswith("/tmp/sandbox") is True. This allows reading or writing files in directories adjacent to the sandbox.
  • Likelihood: Low-Medium — requires the sandbox root to be a path that has a directory-name sibling with the same prefix
  • Priority: Medium

Location

  • File: src/cleveragents/tool/builtins/file_tools.py
  • Function/Class: validate_path()
  • Lines: 76–88

Description

The path traversal check uses str(target).startswith(str(root)) which is a string comparison, not a path comparison. This fails when the resolved target path is in a directory whose name shares a prefix with root.

Evidence

# src/cleveragents/tool/builtins/file_tools.py lines 82-87
def validate_path(path_str: str, sandbox_root: str | None = None) -> Path:
    root = Path(sandbox_root) if sandbox_root else Path.cwd()
    root = root.resolve()
    target = (root / path_str).resolve()
    if not str(target).startswith(str(root)):   # ← STRING prefix, not PATH!
        raise ValueError(f"Path traversal detected: ...")
    return target

Reproduction (confirmed with Python):

>>> import os
>>> from pathlib import Path
>>> root = Path('/tmp/sandbox').resolve()       # e.g., /tmp/sandbox
>>> # If an absolute path like /tmp/sandboxmalicious/file.txt is provided:
>>> target = Path('/tmp/sandboxmalicious/file.txt').resolve()
>>> str(target).startswith(str(root))
True   # ← INCORRECTLY passes! /tmp/sandboxmalicious != /tmp/sandbox

The same pattern was already noted as a prior bug in #7214 (path traversal in validate_path function), but the string prefix issue is distinct — it affects siblings with the same prefix, not just .. traversals.

Expected Behavior

The check should use path.is_relative_to(root) (Python 3.9+) or Path.parents to ensure the path is actually inside the sandbox:

def validate_path(path_str: str, sandbox_root: str | None = None) -> Path:
    root = Path(sandbox_root) if sandbox_root else Path.cwd()
    root = root.resolve()
    target = (root / path_str).resolve()
    # Use is_relative_to for proper path containment (not string prefix)
    if not (target == root or target.is_relative_to(root)):
        raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root")
    return target

Actual Behavior

String prefix comparison allows /tmp/sandboxmalicious/file.txt to pass a sandbox rooted at /tmp/sandbox.

Category

security

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Detection Pool | Agent: bug-hunt-pool-supervisor

## Bug Report: Security — Unsafe Path Containment Check in file_tools.validate_path() ### Severity Assessment - **Impact**: An attacker can craft a `sandbox_root` that is a prefix of a sibling directory. For example, if `sandbox_root=/tmp/sandbox`, a path resolving to `/tmp/sandboxmalicious/file.txt` passes the check because `"/tmp/sandboxmalicious/file.txt".startswith("/tmp/sandbox")` is True. This allows reading or writing files in directories adjacent to the sandbox. - **Likelihood**: Low-Medium — requires the sandbox root to be a path that has a directory-name sibling with the same prefix - **Priority**: Medium ### Location - **File**: `src/cleveragents/tool/builtins/file_tools.py` - **Function/Class**: `validate_path()` - **Lines**: 76–88 ### Description The path traversal check uses `str(target).startswith(str(root))` which is a **string** comparison, not a **path** comparison. This fails when the resolved `target` path is in a directory whose name shares a prefix with `root`. ### Evidence ```python # src/cleveragents/tool/builtins/file_tools.py lines 82-87 def validate_path(path_str: str, sandbox_root: str | None = None) -> Path: root = Path(sandbox_root) if sandbox_root else Path.cwd() root = root.resolve() target = (root / path_str).resolve() if not str(target).startswith(str(root)): # ← STRING prefix, not PATH! raise ValueError(f"Path traversal detected: ...") return target ``` **Reproduction** (confirmed with Python): ```python >>> import os >>> from pathlib import Path >>> root = Path('/tmp/sandbox').resolve() # e.g., /tmp/sandbox >>> # If an absolute path like /tmp/sandboxmalicious/file.txt is provided: >>> target = Path('/tmp/sandboxmalicious/file.txt').resolve() >>> str(target).startswith(str(root)) True # ← INCORRECTLY passes! /tmp/sandboxmalicious != /tmp/sandbox ``` The same pattern was already noted as a prior bug in `#7214` (path traversal in `validate_path` function), but the string prefix issue is distinct — it affects siblings with the same prefix, not just `..` traversals. ### Expected Behavior The check should use `path.is_relative_to(root)` (Python 3.9+) or `Path.parents` to ensure the path is **actually inside** the sandbox: ```python def validate_path(path_str: str, sandbox_root: str | None = None) -> Path: root = Path(sandbox_root) if sandbox_root else Path.cwd() root = root.resolve() target = (root / path_str).resolve() # Use is_relative_to for proper path containment (not string prefix) if not (target == root or target.is_relative_to(root)): raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root") return target ``` ### Actual Behavior String prefix comparison allows `/tmp/sandboxmalicious/file.txt` to pass a sandbox rooted at `/tmp/sandbox`. ### Category security ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Detection Pool | Agent: bug-hunt-pool-supervisor
HAL9000 added this to the v3.2.0 milestone 2026-04-10 19:13:21 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Verified — String prefix path check is a real security vulnerability with a clear reproduction case
  • Priority: Priority/Critical — validate_path() is the core security function used across all file tools; a bypass here affects all file operations
  • Milestone: v3.2.0 — file_tools.py is used in the core execution pipeline; this is a foundational security fix needed before any milestone
  • Type: Type/Bug
  • MoSCoW: Must Have — path validation correctness is a hard security requirement

The fix: replace str(target).startswith(str(root)) with target.is_relative_to(root) (Python 3.9+).

Note: This is related to but distinct from #7214. Both should be fixed together.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

Issue triaged by project owner: - **State**: Verified — String prefix path check is a real security vulnerability with a clear reproduction case - **Priority**: Priority/Critical — `validate_path()` is the core security function used across all file tools; a bypass here affects all file operations - **Milestone**: v3.2.0 — `file_tools.py` is used in the core execution pipeline; this is a foundational security fix needed before any milestone - **Type**: Type/Bug - **MoSCoW**: Must Have — path validation correctness is a hard security requirement The fix: replace `str(target).startswith(str(root))` with `target.is_relative_to(root)` (Python 3.9+). Note: This is related to but distinct from #7214. Both should be fixed together. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#7336
No description provided.