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
12 changes: 12 additions & 0 deletions bin/agent-hook-pre-bash
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ _hook_run_guards() {
_GUARD_GIT_CLEAN_FORCE=0
_GUARD_GIT_PUSH_FORCE=0
_GUARD_GIT_PUSH_MIRROR=0
_GUARD_GIT_PUSH_PRUNE=0
_GUARD_TTY_REQUIRED=0
_GUARD_GIT_COMMIT=0
_GUARD_SL_COMMIT=0
Expand Down Expand Up @@ -515,6 +516,13 @@ _hook_run_guards() {
_GUARD_GIT_PUSH_MIRROR=1
break
;;
--prune)
# --prune deletes remote refs that have no local counterpart.
# (Bare --all is NOT guarded: it only pushes branches and is
# destructive only with --force, which is already caught above.)
_GUARD_GIT_PUSH_PRUNE=1
break
;;
--*) ;;
-?*) [[ "${word#-}" == *f* ]] && {
_GUARD_GIT_PUSH_FORCE=1
Expand Down Expand Up @@ -757,6 +765,10 @@ fi
[ "$_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 git push --prune — deletes remote refs with no local counterpart.
[ "$_GUARD_GIT_PUSH_PRUNE" -eq 1 ] &&
_hook_block 'git push --prune deletes remote refs that have no local branch. Run it manually if the remote deletion is 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
11 changes: 11 additions & 0 deletions test/suites/agent-hook-bash-guards-test
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,17 @@ _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"

# --prune deletes remote refs with no local counterpart — blocked with its own message.
_run_hook "$PRE_BASH" '{"tool_input":{"command":"git push --prune origin refs/heads/*"}}'
_assert_eq "git push --prune: blocked" "2" "$HOOK_EXIT"
_assert_contains "git push --prune: prune-specific message" "--prune deletes remote refs" "$HOOK_STDERR"

# Bare --all only pushes branches (destructive only with --force, already caught),
# so it must NOT be blocked on its own.
_run_hook "$PRE_BASH" '{"tool_input":{"command":"git push --all origin"}}'
_assert_eq "git push --all: allowed" "0" "$HOOK_EXIT"
_assert_not_contains "git push --all: no BLOCKED" "BLOCKED" "$HOOK_STDERR"

# 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