diff --git a/bin/peeragent b/bin/peeragent index 09ab630..d0161e4 100755 --- a/bin/peeragent +++ b/bin/peeragent @@ -7,6 +7,24 @@ RELEASES_URL="https://github.com/nklisch/peeragent/releases" # 1. Explicit override. if [ -n "${PEERAGENT_BIN:-}" ] && [ -x "$PEERAGENT_BIN" ]; then + # Footgun guard: PEERAGENT_BIN must point at the Go BINARY, not at a shim copy + # (this script or plugin/bin/peeragent). A shim starts with "#!"; the real + # Go binaries start with ELF/Mach-O bytes. Pointing PEERAGENT_BIN at a shim + # makes step 1 `exec` the shim again with the same env var -> infinite + # self-exec loop that burns a core and never runs the agent. Detect it, fail + # loudly (never spin). (wants_text isn't defined yet at this point in the + # script, so detect --text inline.) + if [ "$(head -c2 "$PEERAGENT_BIN" 2>/dev/null)" = "#!" ]; then + _wt=0; for _a in "$@"; do [ "$_a" = "--text" ] && _wt=1; done + msg="PEERAGENT_BIN points at a shell shim ($PEERAGENT_BIN), not the Go binary — that would self-exec loop forever. Set PEERAGENT_BIN to the compiled binary (e.g. .../plugin/bin/-/peeragent) or unset it to let this shim auto-detect." + if [ "$_wt" = 1 ]; then + printf '%s\n' "$msg" >&2 + else + _esc=$(printf '%s' "$msg" | sed 's/\\/\\\\\\\\/g; s/"/\\\"/g') + printf '{"status":"failed","summary":"PEERAGENT_BIN points at a shim, not the Go binary (would self-exec loop)","changed_files":[],"verification":[],"details":"%s","metadata":{"exit_code":2}}\n' "$_esc" >&2 + fi + exit 2 + fi exec "$PEERAGENT_BIN" "$@" fi diff --git a/plugin/bin/peeragent b/plugin/bin/peeragent index 09ab630..d0161e4 100755 --- a/plugin/bin/peeragent +++ b/plugin/bin/peeragent @@ -7,6 +7,24 @@ RELEASES_URL="https://github.com/nklisch/peeragent/releases" # 1. Explicit override. if [ -n "${PEERAGENT_BIN:-}" ] && [ -x "$PEERAGENT_BIN" ]; then + # Footgun guard: PEERAGENT_BIN must point at the Go BINARY, not at a shim copy + # (this script or plugin/bin/peeragent). A shim starts with "#!"; the real + # Go binaries start with ELF/Mach-O bytes. Pointing PEERAGENT_BIN at a shim + # makes step 1 `exec` the shim again with the same env var -> infinite + # self-exec loop that burns a core and never runs the agent. Detect it, fail + # loudly (never spin). (wants_text isn't defined yet at this point in the + # script, so detect --text inline.) + if [ "$(head -c2 "$PEERAGENT_BIN" 2>/dev/null)" = "#!" ]; then + _wt=0; for _a in "$@"; do [ "$_a" = "--text" ] && _wt=1; done + msg="PEERAGENT_BIN points at a shell shim ($PEERAGENT_BIN), not the Go binary — that would self-exec loop forever. Set PEERAGENT_BIN to the compiled binary (e.g. .../plugin/bin/-/peeragent) or unset it to let this shim auto-detect." + if [ "$_wt" = 1 ]; then + printf '%s\n' "$msg" >&2 + else + _esc=$(printf '%s' "$msg" | sed 's/\\/\\\\\\\\/g; s/"/\\\"/g') + printf '{"status":"failed","summary":"PEERAGENT_BIN points at a shim, not the Go binary (would self-exec loop)","changed_files":[],"verification":[],"details":"%s","metadata":{"exit_code":2}}\n' "$_esc" >&2 + fi + exit 2 + fi exec "$PEERAGENT_BIN" "$@" fi diff --git a/scripts/validate.sh b/scripts/validate.sh index c0a35e4..40a0e4e 100755 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -188,4 +188,23 @@ printf '%s\n' "$status_output" | grep -q '"status":"failed"' printf '%s\n' "$status_output" | grep -q '"job_id":"missing-job"' printf '%s\n' "$status_output" | grep -q '"exit_code":4' +step "shim PEERAGENT_BIN self-exec-loop guard" +# PEERAGENT_BIN must point at the Go BINARY, not a shim copy. Pointing it at a +# shim makes step 1 `exec` the shim again with the same env var -> infinite +# self-exec loop that burns a core and never runs the agent. The guard detects +# a shebang (shim) target and fails fast (exit 2) instead of spinning. +# Run under `timeout` so a regression (guard removed/broken) hangs the test +# rather than passing by luck. +set +e +loop_output=$(timeout 3 env PEERAGENT_BIN="$ROOT/bin/peeragent" bin/peeragent --status missing-job 2>&1) +loop_code=$? +set -e +if [ "$loop_code" -ne 2 ]; then + echo "expected PEERAGENT_BIN->shim to exit 2 (guard), got $loop_code (124=spun/looped=REGRESSION)" + echo "$loop_output" + exit 1 +fi +printf '%s\n' "$loop_output" | grep -q '"exit_code":2' +printf '%s\n' "$loop_output" | grep -q 'self-exec loop' + step "validation complete"