Skip to content

Commit bbd291f

Browse files
fix: the Hypatia gate could never fire — the defects that made it unconditionally vacuous (#61)
1 parent 28be839 commit bbd291f

1 file changed

Lines changed: 54 additions & 12 deletions

File tree

‎.github/workflows/static-analysis-gate.yml‎

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,28 @@ jobs:
4545
if: steps.install.outputs.installed == 'true'
4646
run: |
4747
set +e
48-
panic-attack assail --format json . > panic-attack-findings.json 2>&1
48+
panic-attack assail --format json . > panic-attack-findings.json
4949
PA_EXIT=$?
5050
set -e
5151
52+
# Same defect class as the Hypatia job below: `2>&1` folded the
53+
# scanner's stderr into the JSON payload, so every jq parse failed,
54+
# every count silently became 0 via `|| echo 0`, and "Fail on critical
55+
# findings" could never fire on any input. Keep stderr on the log.
5256
if [ ! -s panic-attack-findings.json ]; then
5357
echo "[]" > panic-attack-findings.json
5458
fi
5559
60+
# Deliberately a WARNING, not a failure. panic-attack is a downloaded
61+
# release binary whose exit-code and output contract are not verified
62+
# here, and it has no confirmed --exit-zero equivalent, so we surface a
63+
# malformed payload in the log rather than block on an unverified tool.
64+
# Promote to `exit 1` (as the Hypatia job does) once that contract is
65+
# confirmed -- see the follow-up issue linked from this PR.
66+
if ! jq -e 'type == "array"' panic-attack-findings.json >/dev/null 2>&1; then
67+
echo "::warning::panic-attack output is not a JSON array (exit ${PA_EXIT}); counts below are unreliable"
68+
fi
69+
5670
# Parse finding counts
5771
TOTAL=$(jq '. | length' panic-attack-findings.json 2>/dev/null || echo 0)
5872
CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' panic-attack-findings.json 2>/dev/null || echo 0)
@@ -71,13 +85,19 @@ jobs:
7185
if: steps.install.outputs.installed == 'true'
7286
run: |
7387
# Convert JSON findings into GitHub Actions annotations
74-
jq -r '.[] | select(.file != null) |
88+
# Findings carry no `.message` (keys: action,file,line,reason,rule_module,
89+
# severity,type), so every annotation read "null". `.file` is an absolute
90+
# runner path, which GitHub cannot anchor to the diff, so it is made
91+
# workspace-relative here.
92+
jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) |
93+
(.file | ltrimstr($ws + "/")) as $f |
94+
(.reason // .message // .type // "finding") as $m |
7595
if .severity == "critical" then
76-
"::error file=\(.file),line=\(.line // 1)::[panic-attack] \(.message)"
96+
"::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)"
7797
elif .severity == "high" then
78-
"::error file=\(.file),line=\(.line // 1)::[panic-attack] \(.message)"
98+
"::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)"
7999
else
80-
"::warning file=\(.file),line=\(.line // 1)::[panic-attack] \(.message)"
100+
"::warning file=\($f),line=\(.line // 1)::[panic-attack] \($m)"
81101
end
82102
' panic-attack-findings.json || true
83103
@@ -160,12 +180,28 @@ jobs:
160180
if: steps.build.outputs.ready == 'true'
161181
run: |
162182
set +e
163-
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json 2>&1
183+
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json
164184
HYP_EXIT=$?
165185
set -e
166186
167-
if [ ! -s hypatia-findings.json ] || ! jq empty hypatia-findings.json 2>/dev/null; then
168-
echo "[]" > hypatia-findings.json
187+
# --exit-zero is Hypatia's own documented CI recipe (lib/hypatia/cli.ex),
188+
# for exactly this case: "use in CI when a downstream step gates on
189+
# severity counts". Findings go to stdout, the one-line summary to
190+
# stderr, and the process exits 0 unless the SCANNER itself failed.
191+
#
192+
# Do NOT redirect stderr into the payload with `2>&1`: that folds the
193+
# summary line into the JSON, so every parse fails, the old `[]`
194+
# fallback substituted a clean result, CRITICAL was always 0, and the
195+
# gate below could never fire on any input. Keep stderr on the log.
196+
if [ "$HYP_EXIT" -ne 0 ]; then
197+
echo "::error::Hypatia scanner execution failed with exit ${HYP_EXIT}"
198+
exit "$HYP_EXIT"
199+
fi
200+
# `jq empty` is NOT sufficient -- it succeeds on any valid JSON,
201+
# including a bare string, object or null. Assert the array.
202+
if [ ! -s hypatia-findings.json ] || ! jq -e 'type == "array"' hypatia-findings.json >/dev/null; then
203+
echo "::error::Hypatia did not produce a valid JSON findings array"
204+
exit 1
169205
fi
170206
171207
TOTAL=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0)
@@ -183,13 +219,19 @@ jobs:
183219
- name: Emit check annotations
184220
if: steps.build.outputs.ready == 'true'
185221
run: |
186-
jq -r '.[] | select(.file != null) |
222+
# Findings carry no `.message` (keys: action,file,line,reason,rule_module,
223+
# severity,type), so every annotation read "null". `.file` is an absolute
224+
# runner path, which GitHub cannot anchor to the diff, so it is made
225+
# workspace-relative here.
226+
jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) |
227+
(.file | ltrimstr($ws + "/")) as $f |
228+
(.reason // .message // .type // "finding") as $m |
187229
if .severity == "critical" then
188-
"::error file=\(.file),line=\(.line // 1)::[hypatia] \(.message)"
230+
"::error file=\($f),line=\(.line // 1)::[hypatia] \($m)"
189231
elif .severity == "high" then
190-
"::error file=\(.file),line=\(.line // 1)::[hypatia] \(.message)"
232+
"::error file=\($f),line=\(.line // 1)::[hypatia] \($m)"
191233
else
192-
"::warning file=\(.file),line=\(.line // 1)::[hypatia] \(.message)"
234+
"::warning file=\($f),line=\(.line // 1)::[hypatia] \($m)"
193235
end
194236
' hypatia-findings.json || true
195237

0 commit comments

Comments
 (0)