diff --git a/scripts/check-review-verdict.sh b/scripts/check-review-verdict.sh index 1513e9a2d..2b930afb1 100755 --- a/scripts/check-review-verdict.sh +++ b/scripts/check-review-verdict.sh @@ -82,13 +82,30 @@ comments=$(cat) # scanner and simply match nothing — a malformed payload reading as "no verdict posted" is a # fail-OPEN on a gate whose whole job is to withhold approval. # A body containing a NUL is rejected outright: bash strips NULs in command substitution, so +# NOTE the NUL test is `explode | index(0)`, NOT `contains("\u0000")` (ersatztv#647). On jq 1.6 the +# escape truncates the literal to the EMPTY string, and every string contains "" — so that form +# returns true for ALL input, making this guard reject every comment body as malformed. Verified +# against both binaries: 1.6 says true for "hello", 1.7+ says false. The CI runner ships jq 1.6, so +# the whole verdict classifier was inert there. `explode | index(0)` agrees on both. +# # `Review-verdict: MERGEABLE @ ` would arrive at the matcher as a valid verdict line — # text that is not a verdict silently becoming one. +# PARSE CHECK FIRST, separately, because jq's exit codes are not portable enough to distinguish +# "malformed input" from "valid input, no output" (ersatztv#647): jq >= 1.7 exits 5 on a parse error +# while jq 1.6 exits 4 — the SAME code both versions use for "filter produced no output", which is +# the legitimate empty-comment-list case. So on jq 1.6 the check below could not tell a garbage API +# response from "no comments yet", and silently returned `absent` where it should have raised an +# input error. `jq empty` separates the two on every version: non-zero iff the input does not parse, +# regardless of how much output the filter would produce. +if ! printf '%s' "$comments" | jq empty >/dev/null 2>&1; then + printf 'check-review-verdict: stdin is not valid JSON\n' >&2 + exit 2 +fi encoded=$(printf '%s' "$comments" | jq -ce ' if type != "array" then error("not an array") else .[] end | (.body // "") | if type != "string" then error("non-string body") - elif contains("\u0000") then error("NUL in body") + elif (explode | index(0)) != null then error("NUL in body") else . end' 2>/dev/null) jq_rc=$? # `jq -e` exits 4 when a filter produced NO output — which is exactly the legitimate empty-comment-list