BUG-HUNT: [security] InlineToolExecutor._validate_paths uses str.startswith prefix check — path traversal allows escaping sandbox #6588

Open
opened 2026-04-09 21:51:50 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: Security — Sandbox Path Traversal via String Prefix Check

Severity Assessment

  • Impact: An inline tool can read or write files outside the designated sandbox directory, bypassing the primary file-system isolation guard. This is a security-critical path traversal vulnerability.
  • Likelihood: High — any skill author whose inline tool accepts _path, path, or _file input keys can craft a path like /tmp/sandbox-escape/secret.txt when the sandbox is /tmp/sandbox.
  • Priority: Critical

Location

  • File: src/cleveragents/skills/inline_executor.py
  • Function: InlineToolExecutor._validate_paths
  • Lines: ~260–272

Description

The sandbox escape check uses str.startswith() on the string representation of two Path.resolve() results:

# inline_executor.py  lines 264–269
resolved = Path(value).resolve()
sandbox_resolved = sandbox_path.resolve()
if not str(resolved).startswith(str(sandbox_resolved)):
    return (
        f"Path '{value}' for key '{key}' escapes sandbox "
        f"root '{sandbox_path}'"
    )

This is the classic path-prefix bypass: if the sandbox root is /tmp/sandbox, then the path /tmp/sandbox-escape/secret.txt also starts with /tmp/sandbox and passes the check, even though it is outside the sandbox.

Evidence

sandbox_resolved = "/tmp/sandbox"
resolved         = "/tmp/sandbox-escape/secret.txt"

str(resolved).startswith(str(sandbox_resolved))
# → True  ← BYPASS: path is NOT inside sandbox but check passes

The correct check appends a path separator before comparing:

str(resolved).startswith(str(sandbox_resolved) + os.sep)
# or use Path.is_relative_to(sandbox_resolved) (Python ≥ 3.9)

Expected Behavior

A path of /tmp/sandbox-escape/secret.txt should be rejected when the sandbox is /tmp/sandbox.

Actual Behavior

The path passes the sandbox check because "/tmp/sandbox-escape/secret.txt".startswith("/tmp/sandbox") is True.

Suggested Fix

Replace the prefix check with Path.is_relative_to() (Python ≥ 3.9):

try:
    resolved = Path(value).resolve()
    sandbox_resolved = sandbox_path.resolve()
    if not resolved.is_relative_to(sandbox_resolved):
        return (
            f"Path '{value}' for key '{key}' escapes sandbox "
            f"root '{sandbox_path}'"
        )
except (OSError, ValueError):
    return f"Invalid path '{value}' for key '{key}'"

This is the same class of bug already found in file_tools.py (filed separately) — both places use str.startswith for path confinement.

Category

security / boundary

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_, and @tdd_expected_fail to prove the bug exists before fixing it.


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

## Bug Report: Security — Sandbox Path Traversal via String Prefix Check ### Severity Assessment - **Impact**: An inline tool can read or write files outside the designated sandbox directory, bypassing the primary file-system isolation guard. This is a security-critical path traversal vulnerability. - **Likelihood**: High — any skill author whose inline tool accepts `_path`, `path`, or `_file` input keys can craft a path like `/tmp/sandbox-escape/secret.txt` when the sandbox is `/tmp/sandbox`. - **Priority**: Critical ### Location - **File**: `src/cleveragents/skills/inline_executor.py` - **Function**: `InlineToolExecutor._validate_paths` - **Lines**: ~260–272 ### Description The sandbox escape check uses `str.startswith()` on the **string representation** of two `Path.resolve()` results: ```python # inline_executor.py lines 264–269 resolved = Path(value).resolve() sandbox_resolved = sandbox_path.resolve() if not str(resolved).startswith(str(sandbox_resolved)): return ( f"Path '{value}' for key '{key}' escapes sandbox " f"root '{sandbox_path}'" ) ``` This is the classic **path-prefix bypass**: if the sandbox root is `/tmp/sandbox`, then the path `/tmp/sandbox-escape/secret.txt` also starts with `/tmp/sandbox` and **passes the check**, even though it is outside the sandbox. ### Evidence ```python sandbox_resolved = "/tmp/sandbox" resolved = "/tmp/sandbox-escape/secret.txt" str(resolved).startswith(str(sandbox_resolved)) # → True ← BYPASS: path is NOT inside sandbox but check passes ``` The correct check appends a path separator before comparing: ```python str(resolved).startswith(str(sandbox_resolved) + os.sep) # or use Path.is_relative_to(sandbox_resolved) (Python ≥ 3.9) ``` ### Expected Behavior A path of `/tmp/sandbox-escape/secret.txt` should be **rejected** when the sandbox is `/tmp/sandbox`. ### Actual Behavior The path passes the sandbox check because `"/tmp/sandbox-escape/secret.txt".startswith("/tmp/sandbox")` is `True`. ### Suggested Fix Replace the prefix check with `Path.is_relative_to()` (Python ≥ 3.9): ```python try: resolved = Path(value).resolve() sandbox_resolved = sandbox_path.resolve() if not resolved.is_relative_to(sandbox_resolved): return ( f"Path '{value}' for key '{key}' escapes sandbox " f"root '{sandbox_path}'" ) except (OSError, ValueError): return f"Invalid path '{value}' for key '{key}'" ``` This is the same class of bug already found in `file_tools.py` (filed separately) — both places use `str.startswith` for path confinement. ### Category security / boundary ### 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 Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 22:13:18 +00:00
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#6588
No description provided.