BUG-HUNT: [security] GitWorktreeSandbox.get_path() absolute-path argument bypasses traversal guard — actor can write to arbitrary filesystem paths #6514

Open
opened 2026-04-09 21:13:52 +00:00 by HAL9000 · 1 comment
Owner

Bug Report: [security] — get_path() Absolute-Path Bypass

Severity Assessment

  • Impact: An actor receiving an absolute resource_path (e.g., "/etc/passwd") will escape the worktree sandbox entirely and can read/write to arbitrary filesystem locations on the host
  • Likelihood: Medium — any agent that composes paths from user-provided or LLM-generated input could produce an absolute path
  • Priority: Critical

Location

  • File: src/cleveragents/infrastructure/sandbox/git_worktree.py
  • Function: GitWorktreeSandbox.get_path
  • Lines: 290–321

Description

get_path() performs a traversal check by looking for ".." segments in the path split on "/":

if ".." in resource_path.split("/"):
    raise ValueError(f"Path traversal not allowed: {resource_path}")

However this check does not protect against absolute paths. When os.path.join receives an absolute second argument it discards the first:

>>> import os
>>> os.path.join("/tmp/ca-sandbox-abc", "/etc/passwd")
'/etc/passwd'

So a caller passing resource_path = "/etc/passwd" will:

  1. Pass the ..-check (the split ['', 'etc', 'passwd'] does not contain "..").
  2. Receive /etc/passwd back from os.path.join, which is outside the worktree.

Evidence

# git_worktree.py lines 312–321
if ".." in resource_path.split("/"):
    raise ValueError(f"Path traversal not allowed: {resource_path}")

if self._status in (SandboxStatus.CREATED, SandboxStatus.ROLLED_BACK):
    self._status = SandboxStatus.ACTIVE

if self._worktree_path is None:
    raise SandboxStateError("Worktree path not set")

return os.path.join(self._worktree_path, resource_path)  # ← unsafe when resource_path is absolute

Compare with the safe guard in _base.py (_safe_resolve()):

# handlers/_base.py lines 183–188
root = Path(location).resolve()
target = (root / path).resolve()
if target != root and not str(target).startswith(str(root) + os.sep):
    raise PermissionError(f"Path '{path}' escapes resource root '{location}'")
return target

_safe_resolve uses Path.resolve() which:

  • Normalises symlinks
  • Handles absolute paths correctly (they still go through the root-prefix check)
  • Prevents the bypass

get_path() does neither.

Expected Behavior

Any resource_path that would resolve to a location outside the worktree — whether via .., absolute path, or symlink — must raise ValueError / PermissionError.

Actual Behavior

An absolute resource_path (e.g., "/etc/shadow", "/root/.ssh/authorized_keys") passes the guard and is returned verbatim. The caller can then pass this path to a file-write helper, writing outside the sandbox.

Suggested Fix

Replace the hand-rolled check with _safe_resolve() (or equivalent logic using Path.resolve()):

def get_path(self, resource_path: str) -> str:
    ...
    if self._worktree_path is None:
        raise SandboxStateError("Worktree path not set")

    root = Path(self._worktree_path).resolve()
    target = (root / resource_path).resolve()  # absolute path safety handled here
    if target != root and not str(target).startswith(str(root) + os.sep):
        raise ValueError(f"Path '{resource_path}' escapes sandbox root")

    if self._status in (SandboxStatus.CREATED, SandboxStatus.ROLLED_BACK):
        self._status = SandboxStatus.ACTIVE

    return str(target)

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

## Bug Report: [security] — `get_path()` Absolute-Path Bypass ### Severity Assessment - **Impact**: An actor receiving an absolute `resource_path` (e.g., `"/etc/passwd"`) will escape the worktree sandbox entirely and can read/write to arbitrary filesystem locations on the host - **Likelihood**: Medium — any agent that composes paths from user-provided or LLM-generated input could produce an absolute path - **Priority**: Critical ### Location - **File**: `src/cleveragents/infrastructure/sandbox/git_worktree.py` - **Function**: `GitWorktreeSandbox.get_path` - **Lines**: 290–321 ### Description `get_path()` performs a traversal check by looking for `".."` segments in the path split on `"/"`: ```python if ".." in resource_path.split("/"): raise ValueError(f"Path traversal not allowed: {resource_path}") ``` However this check **does not protect against absolute paths**. When `os.path.join` receives an absolute second argument it discards the first: ```python >>> import os >>> os.path.join("/tmp/ca-sandbox-abc", "/etc/passwd") '/etc/passwd' ``` So a caller passing `resource_path = "/etc/passwd"` will: 1. Pass the `..`-check (the split `['', 'etc', 'passwd']` does not contain `".."`). 2. Receive `/etc/passwd` back from `os.path.join`, which is outside the worktree. ### Evidence ```python # git_worktree.py lines 312–321 if ".." in resource_path.split("/"): raise ValueError(f"Path traversal not allowed: {resource_path}") if self._status in (SandboxStatus.CREATED, SandboxStatus.ROLLED_BACK): self._status = SandboxStatus.ACTIVE if self._worktree_path is None: raise SandboxStateError("Worktree path not set") return os.path.join(self._worktree_path, resource_path) # ← unsafe when resource_path is absolute ``` Compare with the safe guard in `_base.py` (`_safe_resolve()`): ```python # handlers/_base.py lines 183–188 root = Path(location).resolve() target = (root / path).resolve() if target != root and not str(target).startswith(str(root) + os.sep): raise PermissionError(f"Path '{path}' escapes resource root '{location}'") return target ``` `_safe_resolve` uses `Path.resolve()` which: - Normalises symlinks - Handles absolute paths correctly (they still go through the root-prefix check) - Prevents the bypass `get_path()` does neither. ### Expected Behavior Any `resource_path` that would resolve to a location outside the worktree — whether via `..`, absolute path, or symlink — must raise `ValueError` / `PermissionError`. ### Actual Behavior An absolute `resource_path` (e.g., `"/etc/shadow"`, `"/root/.ssh/authorized_keys"`) passes the guard and is returned verbatim. The caller can then pass this path to a file-write helper, writing outside the sandbox. ### Suggested Fix Replace the hand-rolled check with `_safe_resolve()` (or equivalent logic using `Path.resolve()`): ```python def get_path(self, resource_path: str) -> str: ... if self._worktree_path is None: raise SandboxStateError("Worktree path not set") root = Path(self._worktree_path).resolve() target = (root / resource_path).resolve() # absolute path safety handled here if target != root and not str(target).startswith(str(root) + os.sep): raise ValueError(f"Path '{resource_path}' escapes sandbox root") if self._status in (SandboxStatus.CREATED, SandboxStatus.ROLLED_BACK): self._status = SandboxStatus.ACTIVE return str(target) ``` ### 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 21:27:52 +00:00
Author
Owner

Issue triaged by project owner:

  • State: Unverified
  • Priority: Critical — PATH TRAVERSAL VULNERABILITY: GitWorktreeSandbox.get_path() absolute-path argument bypasses the traversal guard. An actor can write to arbitrary filesystem paths outside the sandbox.
  • Milestone: v3.2.0 — Security vulnerabilities must be fixed in the earliest milestone
  • MoSCoW: Must Have — Sandbox escape is a critical security issue

Security Impact: The sandbox is supposed to isolate actor file operations to the worktree. If absolute paths bypass the guard, actors can write to /etc/, ~/.ssh/, or any other location on the filesystem. This is a sandbox escape vulnerability.


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

Issue triaged by project owner: - **State**: Unverified - **Priority**: Critical — **PATH TRAVERSAL VULNERABILITY**: `GitWorktreeSandbox.get_path()` absolute-path argument bypasses the traversal guard. An actor can write to arbitrary filesystem paths outside the sandbox. - **Milestone**: v3.2.0 — Security vulnerabilities must be fixed in the earliest milestone - **MoSCoW**: Must Have — Sandbox escape is a critical security issue **Security Impact**: The sandbox is supposed to isolate actor file operations to the worktree. If absolute paths bypass the guard, actors can write to `/etc/`, `~/.ssh/`, or any other location on the filesystem. This is a sandbox escape vulnerability. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner
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#6514
No description provided.