Skip to content

Hypatia detector false positives (workflow_audit + cicd_rules) — spec for upstream fix #370

@hyperpolymath

Description

@hyperpolymath

Context: The Hypatia scan on this repo reports 141 findings (64 critical / 56 high / 21 medium), but after per-finding investigation the large majority are scanner-side false positives or already-honored documented exemptions, not real defects in standards. The genuinely actionable in-repo items (missing timeout-minutes, malformed workflows, mirror SSH presence-gate, an AGPL→MPL header) were fixed in #361/#362/#364/#367 and are on main (383 workflows parse, 0 jobs missing a timeout, registry in sync).

This issue is the hand-off spec for the remaining "foundational/upstream" fixes, which live in hyperpolymath/hypatia (the detector source), not here. Each item below has the file, the false-positive proof, and the exact change.


1. unpinned_action — legacy emitter flags correctly SHA-pinned actions

  • Module/severity: workflow_audit, medium.
  • Symptom: reason: "Action for the check script)\n uses: actions/checkout@de0f needs attention" on governance-reusable.yml. The ref is truncated to @de0f, the text "needs attention" + the "…check script)" prefix shows the detector is grabbing a preceding comment line plus the next uses: line.
  • Proof it's a false positive: every uses: across all 383 workflow files is pinned to a full 40-hex SHA, e.g. actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2.
  • Root cause: the canonical detector WH004 in lib/rules/workflow_hardening.ex is correct — it early-returns on Regex.match?(~r/@[a-fA-F0-9]{40}\b/, slug) and its reason is "pins to a tag/branch — mutable ref allows upstream takeover". The findings here have a different rule_module (workflow_audit) and a different reason format, so a second, legacy unpinned-action emitter exists that (a) doesn't recognize 40-hex SHA pins, (b) truncates the SHA to 4 chars, (c) uses a brittle regex that captures an adjacent comment line.
  • Fix: remove/disable the legacy workflow_audit unpinned-action emitter and route solely through WH004. If it must remain, port WH004's 40-hex skip-guard into it and fix slug extraction to stop at first whitespace (drop trailing # comment) without truncation.

2. secret_action_without_presence_gate — doesn't recognize the if: secrets.X != '' gate

  • Module/severity: workflow_audit, high.
  • Symptom: flags instant-sync.yml (peter-evans/repository-dispatch).
  • Proof it's a false positive: that step is gated — instant-sync.yml line 27: if: ${{ secrets.FARM_DISPATCH_TOKEN != '' }}, with a companion "skip" notice step for the empty case.
  • Root cause: the detector flags any secret-consuming action without recognizing a step-level (or enclosing-job) if: that presence-tests the same secret (!= '').
  • Fix: before emitting, suppress if the step or its job has an if: referencing the same secret with a != '' presence test. (Same pattern now used in mirror-reusable.yml after ci(workflows): presence-gate mirror SSH steps + correct registry-verify licence #367 — it will otherwise keep being false-flagged.)

3. scorecard_publish_with_run_step — not scoped to the publish job

  • Module/severity: workflow_audit, high.
  • Symptom: flags scorecard-enforcer.yml for "split_scorecard_publish_job".
  • Proof it's a false positive: the publish job (scorecard) is already uses-only; the threshold check is already split into a downstream check-score job (needs: scorecard). The file even documents this in a comment.
  • Root cause: the detector scans the whole file for run: steps co-occurring with publish_results: true, instead of scoping to the job that contains the publish step. The run: steps are in other jobs (check-score, check-critical).
  • Fix: only emit if the job that runs ossf/scorecard-action with publish_results: true itself contains a run: step.

4. Carve-out / semantics gaps (cicd_rules + workflow_audit)

  • 4a. banned_language_file (critical) ignores documented path_allow_prefixes. Flags files that CLAUDE.md explicitly exempts:
    • **/bindings/deno/** (interop) → a2ml/bindings/deno/mod.ts, k9-svc/bindings/deno/mod.ts
    • **/vitest.config.ts (tooling) → lol/test/vitest.config.ts
    • the retained Deno archetype scripts/check-ts-allowlist.ts (regression-suite target; documented in docs/EXEMPTION-MECHANISMS.adoc)
    • Ground truth: only 5 banned-language files exist in the repo (1 Python already in .hypatia-baseline.json, 4 TypeScript — all the carve-outs above). The reported "64 critical" is inflated/multi-counted; there are zero genuine violations.
    • Fix: apply the documented TS/RS/npm/JS path_allow_prefixes from CLAUDE.md in the deployed cicd_rules banned-language detector (config has drifted from policy). Consider downgrading grandfathered/in-flight-migration files below critical.
  • 4b. missing_timeout_minutes (medium) fires on reusable-workflow CALLER jobsgovernance.yml, mirror.yml, scorecard.yml, secret-scanner.yml. Their jobs are uses: reusable-workflow calls, where timeout-minutes is invalid YAML and cannot be added; the timeout belongs in the reusable workflow (already present).
    • Fix: skip jobs with a job-level uses: (reusable-workflow calls) in the missing_timeout_minutes check.

General

The reason strings for several finding types are malformed ("Issue in X.yml", embedded raw YAML, truncated refs) — the reason-builder/templating needs hardening so findings are actionable.


Filed as a hand-off; the fixes themselves belong in hyperpolymath/hypatia (lib/rules/workflow_hardening.ex, the workflow_audit module, lib/rules/cicd_rules.ex). This repo's side is complete.

https://claude.ai/code/session_01AmPXB2dA2wCcabo8BXwS28

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions