Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,13 @@ def _looks_like_path(arg: str) -> bool:
def _check_path_jail(argv: Sequence[str], cfg: SandboxConfig) -> str | None:
root = cfg.project_root
for arg in argv[1:]:
if not _looks_like_path(arg):
if not arg or arg.startswith("-"):
continue
if arg.startswith("-"):
# Also jail-check slashless names that exist under root: a symlink like
# `link.txt` -> /etc/passwd has no path separator, so _looks_like_path
# misses it, yet realpath still escapes the jail. lexists() catches the
# symlink even when its target is missing.
if not _looks_like_path(arg) and not os.path.lexists(os.path.join(root, arg)):
continue
# Resolve against root if arg is relative; let absolute paths stay absolute.
candidate = arg
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ def test_flag_arg_skipped(self) -> None:
reason = _check_path_jail(["echo", "-n"], cfg)
self.assertIsNone(reason)

def test_slashless_symlink_to_outside_is_denied(self) -> None:
# A symlink whose name has no slash points outside the root. The module
# advertises a "symlink-safe path jail via realpath prefix check", so the
# jail must resolve and refuse it even though _looks_like_path misses it.
root, cfg = _make_root()
outside_dir = tempfile.mkdtemp(prefix="sandbox-outside-")
secret = os.path.join(outside_dir, "secret.txt")
with open(secret, "w", encoding="utf-8") as fh:
fh.write("TOP-SECRET\n")
os.symlink(secret, os.path.join(root, "link.txt"))
reason = _check_path_jail(["cat", "link.txt"], cfg)
self.assertIsNotNone(reason)
self.assertIn("outside project root", reason)


class TruncateTests(unittest.TestCase):
def test_under_cap_not_truncated(self) -> None:
Expand Down