refactor: simplify exact-file allowlist check

maintainer edit: make the exact-file allowlist check express the invariant directly: the resolved target must still equal the requested logical path before the logical path can match the allowlist.
This commit is contained in:
chengyongru
2026-06-18 00:03:26 +08:00
committed by Xubin Ren
parent 515e418eb4
commit 3f41605ddc
+10 -9
View File
@@ -37,8 +37,8 @@ def _resolve_logical_path(path: str | Path, workspace: str | Path | None = None)
return Path(os.path.abspath(candidate)) return Path(os.path.abspath(candidate))
def _same_logical_path(left: str | Path, right: str | Path) -> bool: def _path_key(path: str | Path) -> str:
return os.path.normcase(os.fspath(left)) == os.path.normcase(os.fspath(right)) return os.path.normcase(os.fspath(path))
def is_path_within(path: str | Path, root: str | Path) -> bool: def is_path_within(path: str | Path, root: str | Path) -> bool:
@@ -63,14 +63,15 @@ def _is_path_exactly_allowed(
files: Iterable[str | Path], files: Iterable[str | Path],
) -> bool: ) -> bool:
"""Return True when *path* resolves exactly to one of the allowed files.""" """Return True when *path* resolves exactly to one of the allowed files."""
logical_key = _path_key(logical_path)
if _path_key(resolved_path) != logical_key:
return False
for file in files: for file in files:
try: try:
allowed_file = _resolve_logical_path(file) allowed_file = _resolve_logical_path(file)
except (OSError, RuntimeError, TypeError, ValueError): except (OSError, RuntimeError, TypeError, ValueError):
continue continue
if not _same_logical_path(logical_path, allowed_file): if _path_key(allowed_file) == logical_key:
continue
if _same_logical_path(resolved_path, allowed_file):
return True return True
return False return False
@@ -102,7 +103,6 @@ def resolve_allowed_path(
strict: bool = False, strict: bool = False,
) -> Path: ) -> Path:
"""Resolve a path and enforce containment in allowed roots when configured.""" """Resolve a path and enforce containment in allowed roots when configured."""
logical = _resolve_logical_path(path, workspace)
resolved = resolve_path(path, workspace, strict=False) resolved = resolve_path(path, workspace, strict=False)
files = list(extra_allowed_files or []) files = list(extra_allowed_files or [])
if allowed_root is None and not files: if allowed_root is None and not files:
@@ -112,11 +112,12 @@ def resolve_allowed_path(
if allowed_root is not None: if allowed_root is not None:
roots.append(allowed_root) roots.append(allowed_root)
roots.extend(extra_allowed_roots or []) roots.extend(extra_allowed_roots or [])
if not is_path_allowed(resolved, roots) and not _is_path_exactly_allowed( exact_allowed = bool(files) and _is_path_exactly_allowed(
logical, _resolve_logical_path(path, workspace),
resolved, resolved,
files, files,
): )
if not is_path_allowed(resolved, roots) and not exact_allowed:
boundary = Path(allowed_root).expanduser() if allowed_root is not None else "allowed files" boundary = Path(allowed_root).expanduser() if allowed_root is not None else "allowed files"
raise WorkspaceBoundaryError( raise WorkspaceBoundaryError(
f"Path {path} is outside allowed directory {boundary}" f"Path {path} is outside allowed directory {boundary}"