From 419119e6171bb7acf5e7237192fdf345a430a422 Mon Sep 17 00:00:00 2001 From: Jacob <20714140+JacobPEvans@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:26:01 +0000 Subject: [PATCH 1/4] fix: add GIT_GUARD_BRANCH_OVERRIDE env-var to _is_on_main_branch (#261) [issue-solver-2026-04-28] --- git-guards/scripts/git-permission-guard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-guards/scripts/git-permission-guard.py b/git-guards/scripts/git-permission-guard.py index 22be195..6f7bc7d 100755 --- a/git-guards/scripts/git-permission-guard.py +++ b/git-guards/scripts/git-permission-guard.py @@ -127,6 +127,9 @@ def _is_on_main_branch() -> bool: Returns False (fail-open) on any error. """ + override = os.environ.get("GIT_GUARD_BRANCH_OVERRIDE") + if override is not None: + return override == "main" try: result = subprocess.run( ["git", "rev-parse", "--show-toplevel"], From 05a0aea592d5761ef55282802f831f7e2287aeb1 Mon Sep 17 00:00:00 2001 From: Jacob <20714140+JacobPEvans@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:26:08 +0000 Subject: [PATCH 2/4] fix: set GIT_GUARD_BRANCH_OVERRIDE for 3 tests that fail on main branch (#261) [issue-solver-2026-04-28] --- git-guards/scripts/test_permission_guard.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git-guards/scripts/test_permission_guard.py b/git-guards/scripts/test_permission_guard.py index fa7a773..10fbab4 100755 --- a/git-guards/scripts/test_permission_guard.py +++ b/git-guards/scripts/test_permission_guard.py @@ -2,6 +2,7 @@ """Tests for git-permission-guard.py ASK/DENY decisions.""" import json +import os import subprocess import sys from pathlib import Path @@ -73,7 +74,9 @@ def check(label: str, cmd: str, expected_decision: str) -> bool: all_pass &= check("git push --force origin main", "git push --force origin main", "deny") # git push --force to a feature branch - must ask (non-main target) +os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = "feature" all_pass &= check("git push --force feature branch", "git push --force origin feature/my-branch", "ask") +os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) # DENY: commit --no-verify all_pass &= check("git commit --no-verify", "git commit -m msg --no-verify", "deny") @@ -92,14 +95,18 @@ def check(label: str, cmd: str, expected_decision: str) -> bool: all_pass &= check("git -C restore ask", "git -C /some/path restore file.txt", "ask") # core.hooksPath precision: value containing the string should not trigger deny +os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = "feature" all_pass &= check("hooksPath in value only", "git -c some.key=echo-core.hooksPath commit -m test", "silent_allow") +os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) # Fallback bypass detection: unrecognised global option before -c breaks loop early all_pass &= check("--no-pager before -c hooksPath", "git --no-pager -c core.hooksPath=/dev/null commit -m msg", "deny") # Fallback also fires when loop parsed a prior -c but broke before a second -c hooksPath all_pass &= check("valid -c then --bare then -c hooksPath", "git -c user.name=test --bare -c core.hooksPath=/dev/null commit -m msg", "deny") # False positive guard: commit message containing the bypass pattern as a substring must not deny +os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = "feature" all_pass &= check("hooksPath in commit message", 'git -c user.name=test commit -m "allow -c core.hooksPath bypass example"', "silent_allow") +os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) print() print("ALL TESTS PASSED" if all_pass else "SOME TESTS FAILED") From b455cd6bb49223a4602c823ba7de3098ae69dff7 Mon Sep 17 00:00:00 2001 From: JacobPEvans <20714140+JacobPEvans@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:20:39 -0400 Subject: [PATCH 3/4] fix(git-guards): safe env-var save/restore + document GIT_GUARD_BRANCH_OVERRIDE - Add branch_override() context manager to preserve/restore any pre-existing GIT_GUARD_BRANCH_OVERRIDE value around each test call - Replace three raw set/pop blocks with 'with branch_override("feature"):' - Add docstring note to _is_on_main_branch() documenting the override path and its CI/test-only intent --- git-guards/scripts/git-permission-guard.py | 4 +++ git-guards/scripts/test_permission_guard.py | 29 ++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/git-guards/scripts/git-permission-guard.py b/git-guards/scripts/git-permission-guard.py index 6f7bc7d..cdb2df1 100755 --- a/git-guards/scripts/git-permission-guard.py +++ b/git-guards/scripts/git-permission-guard.py @@ -126,6 +126,10 @@ def _is_on_main_branch() -> bool: 2. Current branch name == "main" (fallback, git-based) Returns False (fail-open) on any error. + + GIT_GUARD_BRANCH_OVERRIDE: when set, returns (value == "main") without + touching git. Intended for CI/test use only — setting this in production + bypasses the branch guard entirely. """ override = os.environ.get("GIT_GUARD_BRANCH_OVERRIDE") if override is not None: diff --git a/git-guards/scripts/test_permission_guard.py b/git-guards/scripts/test_permission_guard.py index 10fbab4..776ae10 100755 --- a/git-guards/scripts/test_permission_guard.py +++ b/git-guards/scripts/test_permission_guard.py @@ -5,11 +5,25 @@ import os import subprocess import sys +from contextlib import contextmanager from pathlib import Path SCRIPT = Path(__file__).parent / "git-permission-guard.py" +@contextmanager +def branch_override(branch: str): + prev = os.environ.get("GIT_GUARD_BRANCH_OVERRIDE") + os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = branch + try: + yield + finally: + if prev is None: + os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) + else: + os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = prev + + def run(cmd: str) -> dict: inp = json.dumps({"tool_name": "Bash", "tool_input": {"command": cmd}}) result = subprocess.run( @@ -74,9 +88,8 @@ def check(label: str, cmd: str, expected_decision: str) -> bool: all_pass &= check("git push --force origin main", "git push --force origin main", "deny") # git push --force to a feature branch - must ask (non-main target) -os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = "feature" -all_pass &= check("git push --force feature branch", "git push --force origin feature/my-branch", "ask") -os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) +with branch_override("feature"): + all_pass &= check("git push --force feature branch", "git push --force origin feature/my-branch", "ask") # DENY: commit --no-verify all_pass &= check("git commit --no-verify", "git commit -m msg --no-verify", "deny") @@ -95,18 +108,16 @@ def check(label: str, cmd: str, expected_decision: str) -> bool: all_pass &= check("git -C restore ask", "git -C /some/path restore file.txt", "ask") # core.hooksPath precision: value containing the string should not trigger deny -os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = "feature" -all_pass &= check("hooksPath in value only", "git -c some.key=echo-core.hooksPath commit -m test", "silent_allow") -os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) +with branch_override("feature"): + all_pass &= check("hooksPath in value only", "git -c some.key=echo-core.hooksPath commit -m test", "silent_allow") # Fallback bypass detection: unrecognised global option before -c breaks loop early all_pass &= check("--no-pager before -c hooksPath", "git --no-pager -c core.hooksPath=/dev/null commit -m msg", "deny") # Fallback also fires when loop parsed a prior -c but broke before a second -c hooksPath all_pass &= check("valid -c then --bare then -c hooksPath", "git -c user.name=test --bare -c core.hooksPath=/dev/null commit -m msg", "deny") # False positive guard: commit message containing the bypass pattern as a substring must not deny -os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = "feature" -all_pass &= check("hooksPath in commit message", 'git -c user.name=test commit -m "allow -c core.hooksPath bypass example"', "silent_allow") -os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) +with branch_override("feature"): + all_pass &= check("hooksPath in commit message", 'git -c user.name=test commit -m "allow -c core.hooksPath bypass example"', "silent_allow") print() print("ALL TESTS PASSED" if all_pass else "SOME TESTS FAILED") From 6e0331f762685d16c2d45dbb80e5570feb10d1ef Mon Sep 17 00:00:00 2001 From: JacobPEvans <20714140+JacobPEvans@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:49:21 -0400 Subject: [PATCH 4/4] refactor(test): use unittest.mock.patch.dict for env-var save/restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hand-rolled branch_override @contextmanager with a thin wrapper around unittest.mock.patch.dict — stdlib already provides the exact save-and-restore semantics (including key-deletion when previously unset). (claude) --- git-guards/scripts/test_permission_guard.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/git-guards/scripts/test_permission_guard.py b/git-guards/scripts/test_permission_guard.py index 776ae10..abfc6fd 100755 --- a/git-guards/scripts/test_permission_guard.py +++ b/git-guards/scripts/test_permission_guard.py @@ -5,23 +5,14 @@ import os import subprocess import sys -from contextlib import contextmanager from pathlib import Path +from unittest.mock import patch SCRIPT = Path(__file__).parent / "git-permission-guard.py" -@contextmanager def branch_override(branch: str): - prev = os.environ.get("GIT_GUARD_BRANCH_OVERRIDE") - os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = branch - try: - yield - finally: - if prev is None: - os.environ.pop("GIT_GUARD_BRANCH_OVERRIDE", None) - else: - os.environ["GIT_GUARD_BRANCH_OVERRIDE"] = prev + return patch.dict(os.environ, {"GIT_GUARD_BRANCH_OVERRIDE": branch}) def run(cmd: str) -> dict: