From b7b19203858d8f75b11e7bb5f7b593a38709b74b Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Sat, 1 Aug 2026 06:54:33 +0300 Subject: [PATCH] fix(permissions): harden bash rules against repo destruction (#218) * feat(permissions): deny gh api DELETE and repo transfer * feat(permissions): ask on force push and tag delete * test(permissions): cover hardened bash deny/ask rules --------- Co-authored-by: opencode-agent --- .opencode/opencode.json | 21 ++++++++++++++++++++- tests/test_permissions.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/.opencode/opencode.json b/.opencode/opencode.json index 3b62b7d..0768227 100644 --- a/.opencode/opencode.json +++ b/.opencode/opencode.json @@ -276,7 +276,26 @@ "git commit *": "deny", "gh pr create *": "deny", "gh pr merge *": "deny", - "gh issue create *": "deny" + "gh issue create *": "deny", + + "gh api * -X DELETE *": "deny", + "gh api -X DELETE *": "deny", + "gh api * --method DELETE *": "deny", + "gh api * --method delete *": "deny", + "gh repo transfer *": "deny", + + "git push --force*": "ask", + "git push -f*": "ask", + "git push * --force*": "ask", + "git push * -f*": "ask", + "git push * :*": "ask", + "git tag -d *": "ask", + "git -C * push --force*": "ask", + "git -C * push -f*": "ask", + "git -C * push * --force*": "ask", + "git -C * push * -f*": "ask", + "git -C * push * :*": "ask", + "git -C * tag -d *": "ask" } }, "agent": { diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 1d70213..3923802 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -120,6 +120,45 @@ def test_deny_rules_override_earlier_allows(): ) +def test_bash_destructive_deny_ask_rules_present(): + """Harden rules (issue #217) exist in permission.bash with correct actions. + + Deny covers gh api DELETE variants and repo transfer; ask covers force + push, refspec deletion and tag deletion, including ``git -C *`` mirrors. + Also asserts findLast ordering: all new rules come after the last + pre-existing rule (``gh issue create *``), so no earlier allow shadows them. + """ + bash = _load_config()["permission"]["bash"] + expected = { + "gh api * -X DELETE *": "deny", + "gh api -X DELETE *": "deny", + "gh api * --method DELETE *": "deny", + "gh api * --method delete *": "deny", + "gh repo transfer *": "deny", + "git push --force*": "ask", + "git push -f*": "ask", + "git push * --force*": "ask", + "git push * -f*": "ask", + "git push * :*": "ask", + "git tag -d *": "ask", + "git -C * push --force*": "ask", + "git -C * push -f*": "ask", + "git -C * push * --force*": "ask", + "git -C * push * -f*": "ask", + "git -C * push * :*": "ask", + "git -C * tag -d *": "ask", + } + for pattern, action in expected.items(): + assert pattern in bash, f"missing rule: {pattern}" + assert bash[pattern] == action, f"{pattern}: expected {action}, got {bash[pattern]}" + + keys = list(bash.keys()) + last_old_rule = "gh issue create *" + assert keys.index(last_old_rule) < min(keys.index(p) for p in expected), ( + f"new rules must come after '{last_old_rule}' (findLast: last wins)" + ) + + # ── agent.general.tools ────────────────────────────────────────────────────