From 35aa4b914c90154be86f528c11d3c8762aecceee Mon Sep 17 00:00:00 2001 From: Osamaali313 Date: Wed, 15 Jul 2026 23:12:37 +0300 Subject: [PATCH] fix(path_utils): anchor is_safe_path containment on a separator boundary 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. --- fastcode/path_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fastcode/path_utils.py b/fastcode/path_utils.py index 47d96e1f..13c63bae 100644 --- a/fastcode/path_utils.py +++ b/fastcode/path_utils.py @@ -258,8 +258,14 @@ def is_safe_path(self, path: str) -> bool: if resolved is None: # Also check if the joined path would be safe (even if doesn't exist yet) 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) + else: + abs_path = resolved + # Require an exact match or a path-separator boundary, so a sibling + # directory that merely shares the repo-root name prefix (e.g. + # "myrepo_secrets" vs "myrepo") or a "../" escape onto it is not + # treated as inside the repository. + return (abs_path == self.repo_root + or abs_path.startswith(self.repo_root + os.sep)) except Exception as e: self.logger.warning(f"Path security check failed for {path}: {e}") return False