diff --git a/.github/scripts/test-hooks.sh b/.github/scripts/test-hooks.sh index 4554049..187ef7c 100644 --- a/.github/scripts/test-hooks.sh +++ b/.github/scripts/test-hooks.sh @@ -11,6 +11,13 @@ # payload that merely MENTIONS "reset --hard" in a # string field -> exit 0 (regression test for the # no-jq raw-payload over-blocking bug) +# a destructive string quoted, commented or fed to a +# heredoc -> exit 0 (it is data, not a command) +# the same command word reached through sudo, env, +# sh -c, $(...), xargs or find -exec -> exit 2 +# +# Nothing here executes a destructive command: every case is a command STRING +# handed to the guard inside a payload. set -u cd "$(dirname "$0")/../.." || exit 1 @@ -32,7 +39,10 @@ run_with_timeout() { timeout --kill-after=5 "$limit" "$@" return $? fi - "$@" & + # <&0 is load-bearing: without an explicit redirection bash gives an + # asynchronous command /dev/null for stdin, so the hook under test would read + # an empty payload, block nothing, and every case would "pass". + "$@" <&0 & local pid=$! ( sleep "$limit" @@ -142,6 +152,116 @@ else fail "pre-bash-guard exited $rc (expected 0) — over-blocking regression: payload text matched instead of the parsed command" fi +echo "" +echo "=== pre-bash-guard.sh: command word vs command text ===" + +# The guard must judge what a command RUNS, not what its text contains. These +# cases pair each destructive command with a benign one that merely mentions it. +json_escape() { + local s=$1 + s=${s//\\/\\\\} + s=${s//\"/\\\"} + s=${s//$'\n'/\\n} + s=${s//$'\t'/\\t} + printf '%s' "$s" +} + +# guard_case