From 5976f7122a12165ada287a97f9008fb9f4c8d475 Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Fri, 11 Sep 2026 15:13:51 +0530 Subject: [PATCH] Add regression tests for the git hook root guard Executes the shipped snippet directly (extracted from the rebuild body text) rather than a hand copy that could quietly drift from it, covering both the rejected case (a marker pointing outside the repo) and the case the guard must not break (a subdirectory scoped root). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh --- tests/test_hooks.py | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 43943df1a..e672459b5 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -2,6 +2,7 @@ import os import shutil import subprocess +import textwrap from types import SimpleNamespace from pathlib import Path import pytest @@ -332,6 +333,56 @@ def test_rebuild_bodies_with_graphify_root_are_valid_python(): ast.parse(body) +def _extract_root_resolution(body: str) -> str: + """Pull the `.graphify_root` -> `_root` snippet out of a rebuild body, so a + test can execute the shipped logic itself rather than a hand copy that could + quietly drift from it.""" + match = re.search(r"( _root = Path\('\.'\).*?)\n _rebuild_code\(", body, re.DOTALL) + assert match, "root resolution snippet not found" + return textwrap.dedent(match.group(1)) + + +@pytest.mark.parametrize( + "name,body", + [("post-commit", _REBUILD_BODY_COMMIT), ("post-checkout", _REBUILD_BODY_CHECKOUT)], +) +def test_rebuild_bodies_reject_an_out_of_repo_graphify_root(name, body, tmp_path, monkeypatch): + """#3265: `.graphify_root` sits inside graphify-out/, a directory the + documented team workflow says to commit, so its contents are checkout + controlled. Without a bound, a value planted there by a malicious fork or PR + (an absolute path outside the repository) would steer the rebuild -- and so + what gets read, and what gets written into the same committed graphify-out/ + -- to wherever the checkout names, not the repository the hook was installed + into. The recovered root must stay inside the working tree the hook actually + runs from.""" + repo = tmp_path / "repo" + outside = tmp_path / "outside" + (repo / "graphify-out").mkdir(parents=True) + outside.mkdir() + (repo / "graphify-out" / ".graphify_root").write_text(str(outside), encoding="utf-8") + monkeypatch.chdir(repo) + ns = {"Path": Path, "os": os} + exec(compile(_extract_root_resolution(body), "", "exec"), ns) + assert ns["_root"].resolve() == repo.resolve(), f"{name} honoured an out-of-repo root" + + +@pytest.mark.parametrize( + "name,body", + [("post-commit", _REBUILD_BODY_COMMIT), ("post-checkout", _REBUILD_BODY_CHECKOUT)], +) +def test_rebuild_bodies_honour_an_in_repo_graphify_root(name, body, tmp_path, monkeypatch): + """The legitimate case the #3265 guard must not break: a subdirectory-scoped + root (#1173) is still recovered.""" + repo = tmp_path / "repo" + (repo / "graphify-out").mkdir(parents=True) + (repo / "backend").mkdir() + (repo / "graphify-out" / ".graphify_root").write_text("backend", encoding="utf-8") + monkeypatch.chdir(repo) + ns = {"Path": Path, "os": os} + exec(compile(_extract_root_resolution(body), "", "exec"), ns) + assert ns["_root"].resolve() == (repo / "backend").resolve(), f"{name} lost the scoped root" + + @pytest.mark.parametrize( "name,body", [("post-commit", _REBUILD_BODY_COMMIT), ("post-checkout", _REBUILD_BODY_CHECKOUT)],