UAT: Security - validate_path() vulnerable to path prefix collision allowing sandbox escape #4033

Closed
opened 2026-04-06 08:53:23 +00:00 by freemo · 2 comments
Owner

Summary

The validate_path() function in src/cleveragents/tool/builtins/file_tools.py and validate_sandbox_path() in src/cleveragents/skills/builtins/file_ops.py are both vulnerable to a path prefix collision attack that allows an attacker to escape the sandbox root directory.

Metadata

  • Branch: fix/validate-path-prefix-collision
  • Commit Message: fix(tool): use is_relative_to() in validate_path to prevent prefix collision
  • Milestone: Backlog (no milestone — see backlog note below)
  • Parent Epic: #362

Subtasks

  • Fix validate_path() in src/cleveragents/tool/builtins/file_tools.py to use Path.is_relative_to() instead of str.startswith()
  • Fix validate_sandbox_path() in src/cleveragents/skills/builtins/file_ops.py to use Path.is_relative_to() instead of str.startswith()
  • Add regression tests for the prefix collision attack vector
  • Verify fix does not break existing path traversal tests
  • Run nox (all default sessions), fix any errors
  • Verify coverage >= 97% via nox -s coverage_report

Description

Root Cause

Both path validation functions use str(target).startswith(str(root)) to check if a resolved path is within the sandbox root. This check is incorrect because it performs a string prefix match, not a path containment check.

Example of the vulnerability:

  • Sandbox root: /tmp/sandboxABC
  • Attacker-controlled path: /tmp/sandboxABC2/evil.txt
  • str("/tmp/sandboxABC2/evil.txt").startswith("/tmp/sandboxABC")True (incorrectly passes!)
  • Path("/tmp/sandboxABC2/evil.txt").is_relative_to("/tmp/sandboxABC")False (correct)

Affected Code

src/cleveragents/tool/builtins/file_tools.py (line 86):

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 containment
        raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root")
    return target

src/cleveragents/skills/builtins/file_ops.py (line ~47):

def validate_sandbox_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: same issue
        raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root")
    return target

Fix

Replace str(target).startswith(str(root)) with target.is_relative_to(root) in both functions:

if not target.is_relative_to(root):
    raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root")

Steps to Reproduce

import tempfile, os, shutil
from cleveragents.tool.builtins.file_tools import validate_path

sandbox = tempfile.mkdtemp(prefix='sandbox')
sandbox2 = sandbox + '2'
os.makedirs(sandbox2)
evil_file = os.path.join(sandbox2, 'evil.txt')

# This should raise ValueError but does NOT due to the bug
result = validate_path(evil_file, sandbox)
print(f"ESCAPED SANDBOX: {result}")  # Prints the evil path!

Expected Behavior

validate_path() should raise ValueError with "traversal" message when the resolved path is not under the sandbox root.

Actual Behavior

validate_path() allows access to paths that merely share a string prefix with the sandbox root (e.g., /tmp/sandboxABC2/ when sandbox is /tmp/sandboxABC).

Impact

This vulnerability allows an agent to read, write, edit, delete, or list files outside the intended sandbox boundary by crafting paths that share a prefix with the sandbox root directory name. This breaks the core sandbox isolation guarantee of the file tools.

Definition of Done

  • validate_path() in file_tools.py uses is_relative_to()
  • validate_sandbox_path() in file_ops.py uses is_relative_to()
  • Regression test added to features/tool_builtins.feature and features/skill_file_ops.feature
  • All existing path traversal tests still pass
  • All nox stages pass
  • Coverage >= 97%
  • PR merged

Backlog note: This issue was discovered during autonomous operation
on milestone v3.7.0. It does not block milestone completion and has been
placed in the backlog for human review and future milestone assignment.


Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-new-issue-creator

## Summary The `validate_path()` function in `src/cleveragents/tool/builtins/file_tools.py` and `validate_sandbox_path()` in `src/cleveragents/skills/builtins/file_ops.py` are both vulnerable to a **path prefix collision attack** that allows an attacker to escape the sandbox root directory. ## Metadata - **Branch**: `fix/validate-path-prefix-collision` - **Commit Message**: `fix(tool): use is_relative_to() in validate_path to prevent prefix collision` - **Milestone**: Backlog (no milestone — see backlog note below) - **Parent Epic**: #362 ## Subtasks - [ ] Fix `validate_path()` in `src/cleveragents/tool/builtins/file_tools.py` to use `Path.is_relative_to()` instead of `str.startswith()` - [ ] Fix `validate_sandbox_path()` in `src/cleveragents/skills/builtins/file_ops.py` to use `Path.is_relative_to()` instead of `str.startswith()` - [ ] Add regression tests for the prefix collision attack vector - [ ] Verify fix does not break existing path traversal tests - [ ] Run `nox` (all default sessions), fix any errors - [ ] Verify coverage >= 97% via `nox -s coverage_report` ## Description ### Root Cause Both path validation functions use `str(target).startswith(str(root))` to check if a resolved path is within the sandbox root. This check is **incorrect** because it performs a string prefix match, not a path containment check. **Example of the vulnerability:** - Sandbox root: `/tmp/sandboxABC` - Attacker-controlled path: `/tmp/sandboxABC2/evil.txt` - `str("/tmp/sandboxABC2/evil.txt").startswith("/tmp/sandboxABC")` → **True** (incorrectly passes!) - `Path("/tmp/sandboxABC2/evil.txt").is_relative_to("/tmp/sandboxABC")` → **False** (correct) ### Affected Code **`src/cleveragents/tool/builtins/file_tools.py` (line 86):** ```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() if not str(target).startswith(str(root)): # BUG: string prefix, not path containment raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root") return target ``` **`src/cleveragents/skills/builtins/file_ops.py` (line ~47):** ```python def validate_sandbox_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: same issue raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root") return target ``` ### Fix Replace `str(target).startswith(str(root))` with `target.is_relative_to(root)` in both functions: ```python if not target.is_relative_to(root): raise ValueError(f"Path traversal detected: '{path_str}' escapes sandbox root") ``` ### Steps to Reproduce ```python import tempfile, os, shutil from cleveragents.tool.builtins.file_tools import validate_path sandbox = tempfile.mkdtemp(prefix='sandbox') sandbox2 = sandbox + '2' os.makedirs(sandbox2) evil_file = os.path.join(sandbox2, 'evil.txt') # This should raise ValueError but does NOT due to the bug result = validate_path(evil_file, sandbox) print(f"ESCAPED SANDBOX: {result}") # Prints the evil path! ``` ### Expected Behavior `validate_path()` should raise `ValueError` with "traversal" message when the resolved path is not under the sandbox root. ### Actual Behavior `validate_path()` allows access to paths that merely share a string prefix with the sandbox root (e.g., `/tmp/sandboxABC2/` when sandbox is `/tmp/sandboxABC`). ### Impact This vulnerability allows an agent to read, write, edit, delete, or list files outside the intended sandbox boundary by crafting paths that share a prefix with the sandbox root directory name. This breaks the core sandbox isolation guarantee of the file tools. ## Definition of Done - [ ] `validate_path()` in `file_tools.py` uses `is_relative_to()` - [ ] `validate_sandbox_path()` in `file_ops.py` uses `is_relative_to()` - [ ] Regression test added to `features/tool_builtins.feature` and `features/skill_file_ops.feature` - [ ] All existing path traversal tests still pass - [ ] All nox stages pass - [ ] Coverage >= 97% - [ ] PR merged > **Backlog note:** This issue was discovered during autonomous operation > on milestone v3.7.0. It does not block milestone completion and has been > placed in the backlog for human review and future milestone assignment. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-new-issue-creator
Author
Owner

Additional finding: The same path prefix collision vulnerability also exists in src/cleveragents/skills/inline_executor.py (line 266):

# inline_executor.py line 266
if not str(resolved).startswith(str(sandbox_resolved)):
    return (
        f"Path '{value}' for key '{key}' escapes sandbox "
        f"root '{sandbox_path}'"
    )

This is in the _validate_tool_inputs method (or similar) that validates tool inputs against the sandbox path. The same fix applies: replace str(resolved).startswith(str(sandbox_resolved)) with resolved.is_relative_to(sandbox_resolved).

The subtask list in the issue body should be updated to include this third location.


Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-uat-tester

**Additional finding:** The same path prefix collision vulnerability also exists in `src/cleveragents/skills/inline_executor.py` (line 266): ```python # inline_executor.py line 266 if not str(resolved).startswith(str(sandbox_resolved)): return ( f"Path '{value}' for key '{key}' escapes sandbox " f"root '{sandbox_path}'" ) ``` This is in the `_validate_tool_inputs` method (or similar) that validates tool inputs against the sandbox path. The same fix applies: replace `str(resolved).startswith(str(sandbox_resolved))` with `resolved.is_relative_to(sandbox_resolved)`. The subtask list in the issue body should be updated to include this third location. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-uat-tester
Author
Owner

Duplicate of #3960

This issue covers the same validate_path() / validate_sandbox_path() path prefix collision vulnerability using str.startswith() without os.sep suffix. Issue #3960 was filed earlier (2026-04-06T07:53:08Z vs this issue at 2026-04-06T08:53:23Z) and contains a more detailed analysis including the third affected file (inline_executor.py). Closing this as a duplicate — please track the fix in #3960.


Automated by CleverAgents Bot
Supervisor: UAT Testing | Agent: ca-uat-tester

**Duplicate of #3960** This issue covers the same `validate_path()` / `validate_sandbox_path()` path prefix collision vulnerability using `str.startswith()` without `os.sep` suffix. Issue #3960 was filed earlier (2026-04-06T07:53:08Z vs this issue at 2026-04-06T08:53:23Z) and contains a more detailed analysis including the third affected file (`inline_executor.py`). Closing this as a duplicate — please track the fix in #3960. --- **Automated by CleverAgents Bot** Supervisor: UAT Testing | Agent: ca-uat-tester
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.

Blocks
#362 Epic: Security & Safety Hardening
cleveragents/cleveragents-core
Reference
cleveragents/cleveragents-core#4033
No description provided.