|
18 | 18 | RefLog, |
19 | 19 | Reference, |
20 | 20 | RemoteReference, |
| 21 | + Repo, |
21 | 22 | SymbolicReference, |
22 | 23 | TagReference, |
23 | 24 | ) |
|
29 | 30 |
|
30 | 31 |
|
31 | 32 | class TestRefs(TestBase): |
| 33 | + def _repo_with_initial_commit(self, base_dir): |
| 34 | + repo_dir = base_dir / "repo" |
| 35 | + repo = Repo.init(repo_dir) |
| 36 | + (repo_dir / "file.txt").write_text("initial\n", encoding="utf-8") |
| 37 | + repo.index.add(["file.txt"]) |
| 38 | + repo.index.commit("initial") |
| 39 | + return repo |
| 40 | + |
32 | 41 | def test_from_path(self): |
33 | 42 | # Should be able to create any reference directly. |
34 | 43 | for ref_type in (Reference, Head, TagReference, RemoteReference): |
@@ -648,6 +657,88 @@ def test_refs_outside_repo(self): |
648 | 657 | ref_file_name = Path(ref_file.name).name |
649 | 658 | self.assertRaises(BadName, self.rorepo.commit, f"../../{ref_file_name}") |
650 | 659 |
|
| 660 | + def test_reference_create_rejects_path_traversal(self): |
| 661 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 662 | + base_dir = Path(tmp_dir) |
| 663 | + repo = self._repo_with_initial_commit(base_dir) |
| 664 | + outside_path = base_dir / "outside_write.txt" |
| 665 | + |
| 666 | + self.assertRaises(ValueError, Reference.create, repo, "../../../outside_write.txt", "HEAD") |
| 667 | + assert not outside_path.exists() |
| 668 | + |
| 669 | + def test_symbolic_reference_create_rejects_path_traversal(self): |
| 670 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 671 | + base_dir = Path(tmp_dir) |
| 672 | + repo = self._repo_with_initial_commit(base_dir) |
| 673 | + outside_path = base_dir / "outside_write.txt" |
| 674 | + |
| 675 | + self.assertRaises(ValueError, SymbolicReference.create, repo, "../../outside_write.txt", "HEAD") |
| 676 | + assert not outside_path.exists() |
| 677 | + |
| 678 | + def test_symbolic_reference_set_reference_rejects_path_traversal(self): |
| 679 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 680 | + base_dir = Path(tmp_dir) |
| 681 | + repo = self._repo_with_initial_commit(base_dir) |
| 682 | + outside_path = base_dir / "outside_write.txt" |
| 683 | + |
| 684 | + self.assertRaises(ValueError, SymbolicReference(repo, "../../outside_write.txt").set_reference, "HEAD") |
| 685 | + assert not outside_path.exists() |
| 686 | + |
| 687 | + def test_symbolic_reference_rename_rejects_path_traversal(self): |
| 688 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 689 | + base_dir = Path(tmp_dir) |
| 690 | + repo = self._repo_with_initial_commit(base_dir) |
| 691 | + outside_path = base_dir / "outside_move.txt" |
| 692 | + ref = SymbolicReference.create(repo, "SAFE_RENAME_SOURCE", "HEAD") |
| 693 | + |
| 694 | + self.assertRaises(ValueError, ref.rename, "../../outside_move.txt") |
| 695 | + assert not outside_path.exists() |
| 696 | + assert Path(ref.abspath).is_file() |
| 697 | + |
| 698 | + def test_symbolic_reference_delete_rejects_path_traversal(self): |
| 699 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 700 | + base_dir = Path(tmp_dir) |
| 701 | + repo = self._repo_with_initial_commit(base_dir) |
| 702 | + outside_path = base_dir / "outside_delete.txt" |
| 703 | + outside_path.write_text("do not delete\n", encoding="utf-8") |
| 704 | + |
| 705 | + self.assertRaises(ValueError, SymbolicReference.delete, repo, "../../outside_delete.txt") |
| 706 | + assert outside_path.read_text(encoding="utf-8") == "do not delete\n" |
| 707 | + |
| 708 | + def test_symbolic_reference_log_append_rejects_path_traversal(self): |
| 709 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 710 | + base_dir = Path(tmp_dir) |
| 711 | + repo = self._repo_with_initial_commit(base_dir) |
| 712 | + outside_path = base_dir / "outside_reflog.txt" |
| 713 | + |
| 714 | + ref = SymbolicReference(repo, "../../../outside_reflog.txt") |
| 715 | + self.assertRaises(ValueError, ref.log_append, Commit.NULL_BIN_SHA, "do not write", repo.head.commit.binsha) |
| 716 | + assert not outside_path.exists() |
| 717 | + |
| 718 | + def test_remote_reference_delete_cleanup_rejects_path_traversal(self): |
| 719 | + with tempfile.TemporaryDirectory() as tmp_dir: |
| 720 | + base_dir = Path(tmp_dir) |
| 721 | + git_dir = base_dir / "repo" / ".git" |
| 722 | + git_dir.mkdir(parents=True) |
| 723 | + outside_path = base_dir / "outside_remote_delete.txt" |
| 724 | + outside_path.write_text("do not delete\n", encoding="utf-8") |
| 725 | + |
| 726 | + class GitStub: |
| 727 | + def branch(self, *args): |
| 728 | + pass |
| 729 | + |
| 730 | + class RepoStub: |
| 731 | + pass |
| 732 | + |
| 733 | + repo = RepoStub() |
| 734 | + repo.git = GitStub() |
| 735 | + repo.common_dir = str(git_dir) |
| 736 | + repo.git_dir = str(git_dir) |
| 737 | + ref = RemoteReference(repo, "../../outside_remote_delete.txt", check_path=False) |
| 738 | + |
| 739 | + self.assertRaises(ValueError, RemoteReference.delete, repo, ref) |
| 740 | + assert outside_path.read_text(encoding="utf-8") == "do not delete\n" |
| 741 | + |
651 | 742 | def test_validity_ref_names(self): |
652 | 743 | """Ensure ref names are checked for validity. |
653 | 744 |
|
|
0 commit comments