Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bin/agent-hook-pre-bash
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ _hook_run_guards() {
_GUARD_GIT_RESET_HARD=0
_GUARD_GIT_CLEAN_FORCE=0
_GUARD_GIT_PUSH_FORCE=0
_GUARD_GIT_PUSH_MIRROR=0
_GUARD_TTY_REQUIRED=0
_GUARD_GIT_COMMIT=0
_GUARD_SL_COMMIT=0
Expand Down Expand Up @@ -508,6 +509,12 @@ _hook_run_guards() {
_GUARD_GIT_PUSH_FORCE=1
break
;;
--mirror)
# --mirror force-overwrites AND prunes every remote ref, so it is
# remote-destructive even without --force.
_GUARD_GIT_PUSH_MIRROR=1
break
;;
--*) ;;
-?*) [[ "${word#-}" == *f* ]] && {
_GUARD_GIT_PUSH_FORCE=1
Expand Down Expand Up @@ -746,6 +753,10 @@ fi
[ "$_GUARD_GIT_PUSH_FORCE" -eq 1 ] &&
_hook_block 'git push --force rewrites remote history without a lease. Use --force-with-lease for reviewed restacks.'

# Block git push --mirror — force-overwrites and prunes every remote ref.
[ "$_GUARD_GIT_PUSH_MIRROR" -eq 1 ] &&
_hook_block 'git push --mirror force-overwrites and prunes every remote ref. Run it manually if a full mirror is truly intended.'

# Block interactive TTY commands — they will silently hang the agent turn.
[ "$_GUARD_TTY_REQUIRED" -eq 1 ] &&
_hook_block 'This command requires an interactive TTY and will hang. Use a non-interactive alternative (e.g. PAGER=cat, --no-pager, -batch).'
Expand Down
13 changes: 13 additions & 0 deletions test/suites/agent-hook-bash-guards-test
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,19 @@ _assert_not_contains "git push src:dst: no BLOCKED" "BLOCKED" "$HOOK_STDERR"
_run_hook "$PRE_BASH" '{"tool_input":{"command":"git push -u origin feat/foo"}}'
_assert_eq "git push -u: allowed" "0" "$HOOK_EXIT"

# --mirror force-overwrites and prunes every remote ref — blocked, with its own
# (non-force) message so the advice fits.
_run_hook "$PRE_BASH" '{"tool_input":{"command":"git push --mirror"}}'
_assert_eq "git push --mirror: blocked" "2" "$HOOK_EXIT"
_assert_contains "git push --mirror: BLOCKED msg" "BLOCKED" "$HOOK_STDERR"
_assert_contains "git push --mirror: mirror-specific message" "--mirror force-overwrites" "$HOOK_STDERR"

_run_hook "$PRE_BASH" '{"tool_input":{"command":"git push --mirror origin"}}'
_assert_eq "git push --mirror origin: blocked" "2" "$HOOK_EXIT"

_run_hook "$PRE_BASH" '{"tool_input":{"command":"git -C /tmp/repo push --mirror"}}'
_assert_eq "git -C push --mirror: blocked" "2" "$HOOK_EXIT"

# echo of git push --force is prose
_run_hook "$PRE_BASH" '{"tool_input":{"command":"echo git push --force"}}'
_assert_eq "echo git push --force: allowed" "0" "$HOOK_EXIT"
Expand Down
Loading