Skip to content

fix(path_utils): anchor is_safe_path containment on a separator boundary#35

Open
Osamaali313 wants to merge 1 commit into
HKUDS:mainfrom
Osamaali313:fix/is-safe-path-boundary
Open

fix(path_utils): anchor is_safe_path containment on a separator boundary#35
Osamaali313 wants to merge 1 commit into
HKUDS:mainfrom
Osamaali313:fix/is-safe-path-boundary

Conversation

@Osamaali313

Copy link
Copy Markdown

Problem

PathUtils.is_safe_path (fastcode/path_utils.py) — the repository-containment security check delegated to by AgentTools._is_safe_path and used as the first gate in every agent read tool (list_directory, search_codebase, get_file_info, get_file_structure_summary, read_file_content) — tests containment with a bare startswith:

resolved = self.resolve_path(path)
if resolved is None:
    abs_path = os.path.abspath(os.path.join(self.repo_root, path))
    return abs_path.startswith(self.repo_root)
return resolved.startswith(self.repo_root)

self.repo_root has no trailing separator, so any path whose absolute form merely begins with the repo-root string passes — including a sibling directory that shares the name prefix and ../ escapes onto it.

Reproduction

Repo root .../myrepo, sibling .../myrepo_secrets outside it (verified against the real PathUtils):

call before after
is_safe_path("../myrepo_secrets/passwords.txt") True False
is_safe_path("../myrepo_secrets") True False
is_safe_path("../myrepo2") True False
is_safe_path(".") / is_safe_path("sub/f.py") True True

A wrongly-permissive result lets the exploration agent read/list files outside the intended repository boundary.

Fix

Require an exact match or a path-separator boundary:

return (abs_path == self.repo_root
        or abs_path.startswith(self.repo_root + os.sep))

This is the same containment discipline the sibling function file_path_to_module_path in this file already applies (it was deliberately rewritten to use os.path.commonpath, per its comment "prevents path traversal attacks and handles edge cases like root directories") — is_safe_path just wasn't given the same treatment.

Verified RED→GREEN against the real PathUtils (module is stdlib-only); python -m py_compile passes. (No test suite exists in the repo; the reproduction above is the RED→GREEN check.)

is_safe_path tested repository containment with a bare
`resolved.startswith(self.repo_root)` (and the same for the not-yet-existing
branch). Since repo_root has no trailing separator, any path whose absolute
form merely begins with the repo-root string passed the check -- a sibling
directory sharing the name prefix ("myrepo_secrets" vs "myrepo") or a "../"
escape onto one. This is the security gate delegated to by AgentTools._is_
safe_path and used first in every agent read tool (list_directory,
search_codebase, read_file_content, ...), so it let the agent read/list
outside the repo. Require an exact match or a repo_root + os.sep boundary --
the same containment discipline the sibling file_path_to_module_path already
uses via os.path.commonpath.
Copilot AI review requested due to automatic review settings July 15, 2026 20:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants