BUG-HUNT: [security] validate_path uses string prefix check enabling path traversal #7732

Open
opened 2026-04-12 03:21:41 +00:00 by HAL9000 · 3 comments
Owner

Bug Report: Security — validate_path String Prefix Collision Allows Path Traversal

Severity Assessment

  • Impact: An attacker can read/write files outside the declared sandbox root by crafting a path whose string representation starts with the sandbox root string but resolves to a sibling directory.
  • Likelihood: High — any agent or tool invocation that accepts a user-controlled path parameter is exploitable.
  • Priority: Critical

Location

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

Description

The validate_path function performs sandbox containment using Python's str.startswith(). This check is vulnerable to a prefix collision attack: if the sandbox root is /tmp/sandbox, then a resolved path like /tmp/sandbox-escape/secret.txt will satisfy str(target).startswith(str(root)) because the string "/tmp/sandbox-escape/secret.txt" does indeed start with "/tmp/sandbox".

This means any file-read, file-write, file-edit, file-delete, file-list, or file-search tool call can escape the sandbox by targeting a sibling directory whose name starts with the sandbox directory name.

Evidence

# src/cleveragents/tool/builtins/file_tools.py lines 76-88
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)):   # <-- BUG: string prefix, not path prefix
        raise ValueError(f"Path traversal detected: escapes sandbox root")
    return target

Exploit scenario:

  • sandbox_root = "/tmp/sandbox"
  • path_str = "../sandbox-escape/secret.txt"
  • target.resolve() -> /tmp/sandbox-escape/secret.txt
  • "/tmp/sandbox-escape/secret.txt".startswith("/tmp/sandbox") -> True (bypasses check!)

Expected Behavior

The check should ensure the resolved target path is strictly under the sandbox root (i.e., the target equals root or starts with root + "/").

Actual Behavior

A resolved path at /tmp/sandbox-escape/anything passes the sandbox guard when sandbox root is /tmp/sandbox.

Suggested Fix

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 Path.is_relative_to() (Python 3.9+):
    if not target.is_relative_to(root):
        raise ValueError(f"Path traversal detected: {path_str!r} escapes sandbox root")
    return target

Alternatively check str(target) == str(root) or str(target).startswith(str(root) + "/").

Category

security

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: Security — `validate_path` String Prefix Collision Allows Path Traversal ### Severity Assessment - **Impact**: An attacker can read/write files outside the declared sandbox root by crafting a path whose string representation starts with the sandbox root string but resolves to a sibling directory. - **Likelihood**: High — any agent or tool invocation that accepts a user-controlled `path` parameter is exploitable. - **Priority**: Critical ### Location - **File**: `src/cleveragents/tool/builtins/file_tools.py` - **Function/Class**: `validate_path` - **Lines**: 76–88 ### Description The `validate_path` function performs sandbox containment using Python's `str.startswith()`. This check is vulnerable to a **prefix collision attack**: if the sandbox root is `/tmp/sandbox`, then a resolved path like `/tmp/sandbox-escape/secret.txt` will satisfy `str(target).startswith(str(root))` because the string `"/tmp/sandbox-escape/secret.txt"` does indeed start with `"/tmp/sandbox"`. This means any `file-read`, `file-write`, `file-edit`, `file-delete`, `file-list`, or `file-search` tool call can escape the sandbox by targeting a sibling directory whose name starts with the sandbox directory name. ### Evidence ```python # src/cleveragents/tool/builtins/file_tools.py lines 76-88 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)): # <-- BUG: string prefix, not path prefix raise ValueError(f"Path traversal detected: escapes sandbox root") return target ``` **Exploit scenario:** - `sandbox_root = "/tmp/sandbox"` - `path_str = "../sandbox-escape/secret.txt"` - `target.resolve()` -> `/tmp/sandbox-escape/secret.txt` - `"/tmp/sandbox-escape/secret.txt".startswith("/tmp/sandbox")` -> `True` (bypasses check!) ### Expected Behavior The check should ensure the resolved target path is strictly under the sandbox root (i.e., the target equals root or starts with `root + "/"`). ### Actual Behavior A resolved path at `/tmp/sandbox-escape/anything` passes the sandbox guard when sandbox root is `/tmp/sandbox`. ### Suggested Fix ```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 Path.is_relative_to() (Python 3.9+): if not target.is_relative_to(root): raise ValueError(f"Path traversal detected: {path_str!r} escapes sandbox root") return target ``` Alternatively check `str(target) == str(root) or str(target).startswith(str(root) + "/")`. ### Category security ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-12 03:40:49 +00:00
Author
Owner

Verified — Security bug: validate_path uses string prefix check enabling path traversal. MoSCoW: Must-have. Priority: High — security vulnerability.


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

✅ **Verified** — Security bug: validate_path uses string prefix check enabling path traversal. MoSCoW: Must-have. Priority: High — security vulnerability. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Security bug: validate_path uses string prefix check enabling path traversal. MoSCoW: Must-have. Priority: High — security vulnerability.


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

✅ **Verified** — Security bug: validate_path uses string prefix check enabling path traversal. MoSCoW: Must-have. Priority: High — security vulnerability. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Author
Owner

Verified — Security bug: validate_path uses string prefix check enabling path traversal. MoSCoW: Must-have. Priority: High — security vulnerability.


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

✅ **Verified** — Security bug: validate_path uses string prefix check enabling path traversal. MoSCoW: Must-have. Priority: High — security vulnerability. --- **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#7732
No description provided.