113 lines
4.0 KiB
Python
113 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests for immutable, regular-file reads from an untrusted PR commit."""
|
|
|
|
from pathlib import Path
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
SCRIPT = ROOT / "scripts" / "pr-walkthrough-read-file"
|
|
|
|
|
|
class PRWalkthroughReadFileTest(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.repo = Path(self.temp.name) / "repo"
|
|
self.repo.mkdir()
|
|
self.git("init")
|
|
self.git("config", "user.name", "Walkthrough Test")
|
|
self.git("config", "user.email", "walkthrough@example.invalid")
|
|
(self.repo / "src").mkdir()
|
|
(self.repo / "src" / "regular.txt").write_text("trusted commit bytes\n", encoding="utf-8")
|
|
(self.repo / "executable.sh").write_text("#!/bin/sh\n", encoding="utf-8")
|
|
os.chmod(self.repo / "executable.sh", 0o755)
|
|
(self.repo / "binary.dat").write_bytes(b"\xff\xfe")
|
|
(self.repo / "large.txt").write_text("x" * (512 * 1024 + 1), encoding="utf-8")
|
|
os.symlink("/etc/passwd", self.repo / "outside-link")
|
|
self.git("add", ".")
|
|
self.git("commit", "-m", "test fixture")
|
|
self.head = self.git("rev-parse", "HEAD").stdout.strip()
|
|
|
|
def git(self, *args: str) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["git", *args],
|
|
cwd=self.repo,
|
|
text=True,
|
|
capture_output=True,
|
|
check=True,
|
|
)
|
|
|
|
def read(self, path: str, *, head: str | None = None) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--repo",
|
|
str(self.repo),
|
|
"--head-sha",
|
|
head or self.head,
|
|
"--path",
|
|
path,
|
|
],
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
|
|
def test_reads_regular_file_from_exact_commit_not_worktree(self) -> None:
|
|
(self.repo / "src" / "regular.txt").write_text("changed worktree bytes\n", encoding="utf-8")
|
|
|
|
result = self.read("src/regular.txt")
|
|
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout, "trusted commit bytes\n")
|
|
|
|
def test_reads_executable_regular_file(self) -> None:
|
|
result = self.read("executable.sh")
|
|
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertEqual(result.stdout, "#!/bin/sh\n")
|
|
|
|
def test_rejects_traversal_absolute_and_pathspec_paths(self) -> None:
|
|
for path in ("../outside", "/etc/passwd", "src//regular.txt", ":(glob)**"):
|
|
with self.subTest(path=path):
|
|
result = self.read(path)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("invalid repository-relative path", result.stderr)
|
|
|
|
def test_rejects_symlink(self) -> None:
|
|
result = self.read("outside-link")
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("regular file", result.stderr)
|
|
|
|
def test_rejects_binary_file(self) -> None:
|
|
result = self.read("binary.dat")
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("UTF-8 text", result.stderr)
|
|
|
|
def test_rejects_oversized_file(self) -> None:
|
|
result = self.read("large.txt")
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("exceeds 524288 bytes", result.stderr)
|
|
|
|
def test_rejects_missing_file_and_invalid_sha(self) -> None:
|
|
missing = self.read("missing.txt")
|
|
invalid_sha = self.read("src/regular.txt", head="main")
|
|
|
|
self.assertNotEqual(missing.returncode, 0)
|
|
self.assertIn("not a regular file", missing.stderr)
|
|
self.assertNotEqual(invalid_sha.returncode, 0)
|
|
self.assertIn("40 lowercase hexadecimal", invalid_sha.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|