from __future__ import annotations from pathlib import Path class PathSecurityError(ValueError): pass def ensure_inside(path: Path, root: Path) -> Path: resolved = path.expanduser().resolve() root_resolved = root.expanduser().resolve() try: resolved.relative_to(root_resolved) except ValueError as exc: raise PathSecurityError(f"path escapes root: {resolved}") from exc return resolved def resolve_user_path(value: str | None, default: Path, root: Path) -> Path: if value: candidate = Path(value).expanduser() if not candidate.is_absolute(): candidate = root / candidate else: candidate = default return ensure_inside(candidate, root) def rel(path: Path, root: Path) -> str: try: return str(path.resolve().relative_to(root.resolve())) except ValueError: return str(path.resolve())