| 311 | return tuple(roots) |
| 312 | |
| 313 | def ensure_allowed_path(self, path: str | Path) -> Path: |
| 314 | p = Path(path).expanduser() if isinstance(path, str) else path.expanduser() |
| 315 | if not p.is_absolute(): |
| 316 | base = self.cwd or self.workspace_root |
| 317 | p = (base / p).resolve() |
| 318 | else: |
| 319 | p = p.resolve() |
| 320 | # Mirror TS ``shouldBypassPermissions`` at |
| 321 | # ``typescript/src/utils/permissions/permissions.ts:1268-1281``: |
| 322 | # bypassPermissions mode (set by --dangerously-skip-permissions), |
| 323 | # or plan mode when the user started with bypass available, |
| 324 | # short-circuits the working-directory allowlist so the tool can |
| 325 | # operate outside ``workspace_root``. |
| 326 | mode = self.permission_context.mode |
| 327 | if mode == "bypassPermissions" or ( |
| 328 | mode == "plan" |
| 329 | and self.permission_context.is_bypass_permissions_mode_available |
| 330 | ): |
| 331 | return p |
| 332 | roots = self.allowed_roots() |
| 333 | if any(_is_within(p, root) for root in roots): |
| 334 | return p |
| 335 | roots_str = ", ".join(str(r) for r in roots) |
| 336 | raise ToolPermissionError(f"path is outside allowed working directories: {p} (allowed: {roots_str})") |
| 337 | |
| 338 | def ensure_readable_path(self, path: str | Path) -> Path: |
| 339 | """Like :meth:`ensure_allowed_path` but also permits harness-internal |