Skip to content

Upstream fixes to green Hypatia: restore advisory scan-guard (standards) + 3 scanner false-positive rules (hypatia) #28

Description

@hyperpolymath

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:

  1. The job fails purely on the scan line's exit code.
  2. 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

  • hyperpolymath/standards: Fix 1 (scan-guard || true) — greens the check estate-wide
  • hyperpolymath/hypatia: Fix 2 (SC-014 CodeQL detection)
  • hyperpolymath/hypatia: Fix 3 (SC-013 strip YAML comments)
  • hyperpolymath/hypatia: Fix 4 (WF017 two-step gate)
  • Confirm exact source text + mix test in hypatia before merge

Background: #26 (standards bump), #27 (in-repo cleanup, 11 → 3 findings).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions