From b227ec1f733010576fac134c56a38f8b239e5fb0 Mon Sep 17 00:00:00 2001 From: Flo Date: Mon, 30 Mar 2026 23:20:54 +0200 Subject: [PATCH] feat: add pre-execution safety review prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Asks users to confirm they've reviewed the script before it modifies config. On decline, prints instructions for piping the script to Claude Code or Gemini CLI for a security review. Skipped with -y and --audit flags. 3 new tests (53 total). Closes: #7 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- git-harden.sh | 34 ++++++++++++++++++++++++++++++++++ test/git-harden.bats | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/git-harden.sh b/git-harden.sh index a117466..5c662ec 100755 --- a/git-harden.sh +++ b/git-harden.sh @@ -903,12 +903,46 @@ print_admin_recommendations() { printf '\n' >&2 } +# ------------------------------------------------------------------------------ +# Safety review gate +# ------------------------------------------------------------------------------ + +safety_review_gate() { + # Skip in -y mode (user takes responsibility) or --audit (read-only) + if [ "$AUTO_YES" = true ] || [ "$AUDIT_ONLY" = true ]; then + return + fi + + local script_path + script_path="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")" + + printf '\n%b── Safety Review ──%b\n' "$BOLD" "$RESET" >&2 + printf ' Before running scripts that modify your system configuration,\n' >&2 + printf ' you should review them with a tool you trust.\n\n' >&2 + + if ! prompt_yn "Have you reviewed this script (or had an AI assistant review it) for safety?"; then + printf '\n You can review this script by piping it to an AI coding assistant:\n\n' >&2 + printf ' %bClaude Code:%b\n' "$BOLD" "$RESET" >&2 + printf ' cat "%s" | claude "Review this shell script for security issues.\n' "$script_path" >&2 + printf ' Check that it only modifies git and SSH config, makes no network\n' >&2 + printf ' calls, and does not exfiltrate data. List every file it writes to."\n\n' >&2 + printf ' %bGemini CLI:%b\n' "$BOLD" "$RESET" >&2 + printf ' cat "%s" | gemini "Review this shell script for security issues.\n' "$script_path" >&2 + printf ' Check that it only modifies git and SSH config, makes no network\n' >&2 + printf ' calls, and does not exfiltrate data. List every file it writes to."\n\n' >&2 + printf ' %bManual review:%b\n' "$BOLD" "$RESET" >&2 + printf ' less "%s"\n\n' "$script_path" >&2 + exit 0 + fi +} + # ------------------------------------------------------------------------------ # Main # ------------------------------------------------------------------------------ main() { parse_args "$@" + safety_review_gate detect_platform check_dependencies diff --git a/test/git-harden.bats b/test/git-harden.bats index c958205..dd02484 100755 --- a/test/git-harden.bats +++ b/test/git-harden.bats @@ -619,6 +619,44 @@ SSHEOF grep -q "transfer.fsckobjects=true" "$backup_file" } +# =========================================================================== +# Safety review gate +# =========================================================================== + +@test "safety gate is skipped with -y" { + source_functions + AUTO_YES=true + AUDIT_ONLY=false + + run safety_review_gate + assert_success + refute_output --partial "Safety Review" +} + +@test "safety gate is skipped with --audit" { + source_functions + AUTO_YES=false + AUDIT_ONLY=true + + run safety_review_gate + assert_success + refute_output --partial "Safety Review" +} + +@test "safety gate exits 0 with instructions when user says no" { + source_functions + AUTO_YES=false + AUDIT_ONLY=false + + # Override prompt_yn to simulate "no" answer + prompt_yn() { return 1; } + + run safety_review_gate + assert_success # exit 0, not an error + assert_output --partial "claude" + assert_output --partial "gemini" +} + # =========================================================================== # End-to-end: --audit mode # ===========================================================================