diff --git a/src/path_scope.py b/src/path_scope.py index 31828a6a..bf4e3005 100644 --- a/src/path_scope.py +++ b/src/path_scope.py @@ -59,6 +59,8 @@ class WorkspacePathScope: def validate_path(self, candidate: str | Path, cwd: str | Path | None = None) -> PathScopeDecision: raw = os.path.expandvars(os.path.expanduser(str(candidate))) + if raw == os.devnull: + return PathScopeDecision(True, 'null device is outside filesystem scope', str(candidate), raw) if _is_windows_absolute(raw): return self._validate_windows_path(raw) base = Path(cwd).expanduser().resolve(strict=False) if cwd else self.roots[0] diff --git a/tests/test_security_scope.py b/tests/test_security_scope.py index 59275dda..9d03c1af 100644 --- a/tests/test_security_scope.py +++ b/tests/test_security_scope.py @@ -94,6 +94,15 @@ class WorkspacePathScopeTests(unittest.TestCase): self.assertFalse(decision.allowed) self.assertIn(str(outside.resolve()), decision.resolved or '') + def test_null_device_redirection_is_allowed(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + workspace = Path(tmp) / 'workspace' + workspace.mkdir() + + decision = WorkspacePathScope.from_root(workspace).validate_payload('echo ok >/dev/null') + + self.assertTrue(decision.allowed, decision.reason) + def test_explicit_worktree_roots_are_allowed(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp)