Summary
After #26 bumped the standards reusables, the Hypatia scanner upgraded and the hypatia / Hypatia Neurosymbolic Analysis check went red. #27 cleared the genuine in-repo findings (count 11 → 3, critical=0). The remaining red is not caused by anything in this repo — it's one upstream regression plus three scanner false-positive rules. This issue tracks those four fixes.
⚠️ These fixes live in other repositories (hyperpolymath/standards, hyperpolymath/hypatia). They can't be pushed from this repo's session scope, so this is a tracking / handoff issue. The patches were written against source read read-only (WebFetch) — confirm exact surrounding text and run mix test when applying.
Root cause of the red check
hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml runs the scan step's run: block under GitHub Actions' default bash -eo pipefail. The scan line (≈L49):
HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json
hypatia-cli.sh exits 1 whenever there is any ≥medium finding (it's meant to be advisory). Under -e, that aborts the step before the jq severity counts and the >> $GITHUB_OUTPUT lines execute. Two consequences:
- The job fails purely on the scan line's exit code.
steps.scan.outputs.critical is never written, so the downstream advisory step (Check for critical issues, if: steps.scan.outputs.critical > 0, commented "Warn but don't fail — fix forward") is currently dead code.
Verified scan output on this repo after #27:
[hypatia] scan complete: 3 findings >= medium (critical=0, high=1, medium=2, low=0, info=0); exit 1
All 3 remaining are false-positives (Fixes 2–4 below).
Fix 1 — hyperpolymath/standards: restore the scan guard (one line)
File: .github/workflows/hypatia-scan-reusable.yml (≈L49)
- HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json
+ HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json || true
Lets the step continue past the scan → severity counts + $GITHUB_OUTPUT get written, the $GITHUB_STEP_SUMMARY table renders, and the Check for critical issues advisory gate finally works as its own comment intends. This is what greens hypatia across the whole estate. (--exit-zero is the original idiom; || true is flag-agnostic and guaranteed. JSON is written to the file before the CLI's non-zero exit, so jq still has real data.)
Fix 2 — hyperpolymath/hypatia SC-014 false positive
File: lib/scorecard_ingestor.ex · check_sast (≈L297)
Bug: the "CodeQL workflow" is chosen by finding the first file whose contents contain the substring "codeql". governance.yml's docstring ("…rust-ci, codeql, dependabot…") wins over the real codeql.yml, so codeql_effective? runs on the wrong file (no language: actions) → false "Nominal-only SAST." The real codeql.yml declares language: actions + javascript-typescript.
Fix: keep {filename, content} pairs and identify the CodeQL workflow by actually invoking the CodeQL action (or the canonical filename):
named_contents =
files
|> Enum.flat_map(fn f ->
case File.read(Path.join(workflows_dir, f)) do
{:ok, c} -> [{f, c}]
_ -> []
end
end)
contents = Enum.map(named_contents, fn {_f, c} -> c end) # has_non_codeql_sast unchanged
# A workflow *is* CodeQL only if it invokes the action (or is codeql.yml) —
# not because some other workflow's comment mentions the word "codeql".
codeql_content =
Enum.find_value(named_contents, fn {f, c} ->
cond do
String.contains?(c, "github/codeql-action") -> c
Path.basename(f) in ["codeql.yml", "codeql.yaml"] -> c
true -> nil
end
end)
The cond branches and codeql_effective?/2 stay as-is — they now receive the correct file.
Fix 3 — hyperpolymath/hypatia SC-013 false positive
File: lib/scorecard_ingestor.ex · check_pinned_dependencies (≈L418)
Bug: ~r/uses:\s+[\w-]+\/[\w-]+@v\d/ runs on raw content, so commented-out examples (# - uses: actions/upload-artifact@v4 in release.yml) count as tag-pinned. All live actions are SHA-pinned.
Fix: strip comments before matching (add a small local helper; the existing strip_comments/1 is in another module):
case File.read(path) do
{:ok, content} ->
Regex.match?(~r/uses:\s+[\w-]+\/[\w-]+@v\d/, strip_yaml_comments(content))
_ -> false
end
# Drop YAML comments so commented-out `# - uses: foo@v4` examples don't count
# as tag-pinned. Strips from the first `#` per line — fine for `uses:` (action
# refs contain no `#`); a real pin with a trailing comment still matches.
defp strip_yaml_comments(content) do
content
|> String.split("\n")
|> Enum.map(&String.replace(&1, ~r/#.*$/, ""))
|> Enum.join("\n")
end
Fix 4 — hyperpolymath/hypatia WF017 false positive
File: lib/rules/workflow_audit.ex · check_ungated_secret_action (≈L1151)
Bug: the gate regex only matches a same-step if: … secrets.X != ''. instant-sync.yml uses the documented two-step gate, so it's wrongly flagged:
- id: gate
run: if [ -n "${{ secrets.FARM_DISPATCH_TOKEN }}" ]; then echo "has_token=true" >> "$GITHUB_OUTPUT"; fi
- uses: peter-evans/repository-dispatch@…
if: steps.gate.outputs.has_token == 'true'
Fix: also treat the step as gated when its if: consumes an output from a gate step present in the same workflow (stripped — full file text — is already in scope in that closure):
if Regex.match?(gate_re, step_block) or output_gated?(step_block, stripped) do
[]
else
[%{rule: "WF017", ...}] # finding unchanged
end
# Recognize the two-step presence gate: a separate `id: <gate>` step exports an
# output and the secret-consuming step guards on `if: steps.<gate>.outputs.<name>`.
# (The pattern in standards#305 / instant-sync.yml.)
defp output_gated?(step_block, full_content) do
case Regex.run(~r/if:[^\n]*steps\.([A-Za-z0-9_-]+)\.outputs\.[A-Za-z0-9_-]+/, step_block) do
[_full, gate_id] -> Regex.match?(~r/\bid:\s*#{Regex.escape(gate_id)}\b/, full_content)
_ -> false
end
end
Optional stricter variant: also confirm the gate step's body references secrets.<secret_name> (needs per-step segmentation — more fragile; the above kills the FP with negligible false-negative risk for an advisory rule).
Checklist
Background: #26 (standards bump), #27 (in-repo cleanup, 11 → 3 findings).
Summary
After #26 bumped the standards reusables, the Hypatia scanner upgraded and the
hypatia / Hypatia Neurosymbolic Analysischeck went red. #27 cleared the genuine in-repo findings (count 11 → 3,critical=0). The remaining red is not caused by anything in this repo — it's one upstream regression plus three scanner false-positive rules. This issue tracks those four fixes.Root cause of the red check
hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.ymlruns the scan step'srun:block under GitHub Actions' defaultbash -eo pipefail. The scan line (≈L49):hypatia-cli.shexits 1 whenever there is any ≥medium finding (it's meant to be advisory). Under-e, that aborts the step before thejqseverity counts and the>> $GITHUB_OUTPUTlines execute. Two consequences:steps.scan.outputs.criticalis never written, so the downstream advisory step (Check for critical issues,if: steps.scan.outputs.critical > 0, commented "Warn but don't fail — fix forward") is currently dead code.Verified scan output on this repo after #27:
All 3 remaining are false-positives (Fixes 2–4 below).
Fix 1 —
hyperpolymath/standards: restore the scan guard (one line)File:
.github/workflows/hypatia-scan-reusable.yml(≈L49)Lets the step continue past the scan → severity counts +
$GITHUB_OUTPUTget written, the$GITHUB_STEP_SUMMARYtable renders, and theCheck for critical issuesadvisory gate finally works as its own comment intends. This is what greenshypatiaacross the whole estate. (--exit-zerois the original idiom;|| trueis flag-agnostic and guaranteed. JSON is written to the file before the CLI's non-zero exit, sojqstill has real data.)Fix 2 —
hyperpolymath/hypatiaSC-014 false positiveFile:
lib/scorecard_ingestor.ex·check_sast(≈L297)Bug: the "CodeQL workflow" is chosen by finding the first file whose contents contain the substring
"codeql".governance.yml's docstring ("…rust-ci, codeql, dependabot…") wins over the realcodeql.yml, socodeql_effective?runs on the wrong file (nolanguage: actions) → false "Nominal-only SAST." The realcodeql.ymldeclareslanguage: actions+javascript-typescript.Fix: keep
{filename, content}pairs and identify the CodeQL workflow by actually invoking the CodeQL action (or the canonical filename):The
condbranches andcodeql_effective?/2stay as-is — they now receive the correct file.Fix 3 —
hyperpolymath/hypatiaSC-013 false positiveFile:
lib/scorecard_ingestor.ex·check_pinned_dependencies(≈L418)Bug:
~r/uses:\s+[\w-]+\/[\w-]+@v\d/runs on raw content, so commented-out examples (# - uses: actions/upload-artifact@v4inrelease.yml) count as tag-pinned. All live actions are SHA-pinned.Fix: strip comments before matching (add a small local helper; the existing
strip_comments/1is in another module):Fix 4 —
hyperpolymath/hypatiaWF017 false positiveFile:
lib/rules/workflow_audit.ex·check_ungated_secret_action(≈L1151)Bug: the gate regex only matches a same-step
if: … secrets.X != ''.instant-sync.ymluses the documented two-step gate, so it's wrongly flagged:Fix: also treat the step as gated when its
if:consumes an output from a gate step present in the same workflow (stripped— full file text — is already in scope in that closure):Optional stricter variant: also confirm the gate step's body references
secrets.<secret_name>(needs per-step segmentation — more fragile; the above kills the FP with negligible false-negative risk for an advisory rule).Checklist
hyperpolymath/standards: Fix 1 (scan-guard|| true) — greens the check estate-widehyperpolymath/hypatia: Fix 2 (SC-014 CodeQL detection)hyperpolymath/hypatia: Fix 3 (SC-013 strip YAML comments)hyperpolymath/hypatia: Fix 4 (WF017 two-step gate)mix testinhypatiabefore mergeBackground: #26 (standards bump), #27 (in-repo cleanup, 11 → 3 findings).