fix(ci): scorecard-enforcer reads the real OSSF score (JSON, not SARIF)#359
Merged
Conversation
🔍 Hypatia Security ScanFindings: 222 issues detected
View findings[
{
"reason": "Action for the check script)\n uses: actions/checkout@de0f needs attention",
"type": "unpinned_action",
"file": "governance-reusable.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action for the check script)\n uses: actions/checkout@de0f needs attention",
"type": "unpinned_action",
"file": "governance-reusable.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in affinescript-verify.yml",
"type": "missing_timeout_minutes",
"file": "affinescript-verify.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in boj-build.yml",
"type": "missing_timeout_minutes",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in changelog-reusable.yml",
"type": "missing_timeout_minutes",
"file": "changelog-reusable.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql-reusable.yml",
"type": "missing_timeout_minutes",
"file": "codeql-reusable.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "missing_timeout_minutes",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in deno-ci-reusable.yml",
"type": "missing_timeout_minutes",
"file": "deno-ci-reusable.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
🔍 Hypatia Security ScanFindings: 222 issues detected
View findings[
{
"reason": "Action for the check script)\n uses: actions/checkout@de0f needs attention",
"type": "unpinned_action",
"file": "governance-reusable.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action for the check script)\n uses: actions/checkout@de0f needs attention",
"type": "unpinned_action",
"file": "governance-reusable.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in affinescript-verify.yml",
"type": "missing_timeout_minutes",
"file": "affinescript-verify.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in boj-build.yml",
"type": "missing_timeout_minutes",
"file": "boj-build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in casket-pages.yml",
"type": "missing_timeout_minutes",
"file": "casket-pages.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in changelog-reusable.yml",
"type": "missing_timeout_minutes",
"file": "changelog-reusable.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql-reusable.yml",
"type": "missing_timeout_minutes",
"file": "codeql-reusable.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in codeql.yml",
"type": "missing_timeout_minutes",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in deno-ci-reusable.yml",
"type": "missing_timeout_minutes",
"file": "deno-ci-reusable.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
#360 added timeout-minutes estate-wide and fixed changelog-reusable.yml, but could not touch boj-build.yml because it does not parse: two trailing steps (K9-SVC Validation, Contractile Check) had dropped to 2-space indentation, out of the 'steps:' list, so GitHub fails to compile it on every push (startup_failure, 0 jobs). Re-indent those steps to 6 spaces and add the job-level timeout-minutes the estate convention expects. The job stays gated on 'if: vars.BOJ_SERVER_URL != ...', so it skips cleanly when unconfigured. Validated with PyYAML. Addresses the repair side of standards#331. No SPDX header or licence content touched. https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82
…ilure)
GitHub does not expose the `secrets` context to `if:` expressions (only
vars/env/inputs/github/needs). Any workflow with `if: ${{ secrets.* }}`
fails to compile and startup_failures on every push — which is why these
ran red on main with 0 jobs:
- boj-build.yml — job `if: ... secrets.BOJ_SERVER_URL ...` → gate on the
`vars` form only (the step still reads secret-or-var
for the URL value). Job now skips cleanly when BOJ is
unconfigured. Completes the boj-build repair (#331).
- instant-sync.yml — 2 step `if: secrets.FARM_DISPATCH_TOKEN ...` → map the
token to a job-level `env` and gate on `env.*`.
- mirror-reusable.yml — 4 step `if: secrets.RADICLE_KEY ...` in mirror-radicle
→ same env-mapping. This also fixes the `mirror.yml`
caller, which startup_failed because its callee did.
env IS available in if:, so the skip-clean behaviour the comments describe now
actually works. Validated with PyYAML; no remaining `secrets.` in any if:.
No SPDX header or licence content touched.
https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82
…ob-if
rust-ci-reusable.yml and elixir-ci-reusable.yml put
`hashFiles(format('{0}/<manifest>', inputs.working_directory)) != ''`
directly in each job-level `if:`. That startup-failed the reusable on every
push (run with 0 jobs): the `inputs` context does not exist when GitHub
compiles a workflow for a non-workflow_call event, so the expression is
invalid. (It was also latently broken when *called* — `hashFiles` at job
level runs before any checkout, so it reads an empty workspace.)
Replace with the canonical pattern: a small `detect` job checks out first,
resolves `has_cargo`/`has_mix` (and the enable_audit/enable_coverage toggles
for Rust) into step outputs, and every downstream job gates on
`needs.detect.outputs.*` — valid in every event context and actually sees the
files. No existing job names change, so consumer status-check names are
unaffected.
This is an estate-consumed template change (separate commit for independent
review). Validated with PyYAML. No SPDX header or licence content touched.
https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82
20a03c6 to
a85135d
Compare
…ARIF field The check-score gate read `jq '.runs[0].tool.driver.properties.score'` from the SARIF artifact. The OSSF Scorecard SARIF does not carry the aggregate score at that path, so jq's `// 0` fallback made the gate read 0 on EVERY push and fail `MIN_SCORE=5` unconditionally — it never measured real posture. Logs from the 2026-06-03 main run confirm: the `scorecard` job + all its steps succeed; only `check-score` fails with 'Scorecard Score: 0'. The aggregate score lives in scorecard's JSON output. Fix check-score to run scorecard-action with `results_format: json` + `publish_results: false` (so the job needs no OIDC/id-token) and read `.score`. The `scorecard` job is unchanged and stays uses-only (OSSF publish contract); its now-unused SARIF artifact persist step is removed. MIN_SCORE=5 is unchanged — posture policy untouched. NOTE: this makes the gate ACCURATE, not necessarily green — if the repo's real score is < 5 the gate will (correctly) fail, which is then a genuine posture finding for the owner to act on, not a workflow bug. Separate commit so this security-gate change can be reviewed/dropped independently. No SPDX/licence edit. https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82
…urnNU # Conflicts: # .github/workflows/boj-build.yml # .github/workflows/elixir-ci-reusable.yml # .github/workflows/instant-sync.yml # .github/workflows/mirror-reusable.yml # .github/workflows/rust-ci-reusable.yml
🔍 Hypatia Security ScanFindings: 139 issues detected
View findings[
{
"reason": "Action for the check script)\n uses: actions/checkout@de0f needs attention",
"type": "unpinned_action",
"file": "governance-reusable.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Action for the check script)\n uses: actions/checkout@de0f needs attention",
"type": "unpinned_action",
"file": "governance-reusable.yml",
"action": "pin_sha",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in governance.yml",
"type": "missing_timeout_minutes",
"file": "governance.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in mirror.yml",
"type": "missing_timeout_minutes",
"file": "mirror.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in scorecard.yml",
"type": "missing_timeout_minutes",
"file": "scorecard.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in secret-scanner.yml",
"type": "missing_timeout_minutes",
"file": "secret-scanner.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in scorecard-enforcer.yml",
"type": "scorecard_publish_with_run_step",
"file": "scorecard-enforcer.yml",
"action": "split_scorecard_publish_job",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in instant-sync.yml",
"type": "secret_action_without_presence_gate",
"file": "instant-sync.yml",
"action": "peter-evans/repository-dispatch",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/standards/standards/lol/test/vitest.config.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/standards/standards/k9-svc/bindings/deno/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
hyperpolymath
added a commit
that referenced
this pull request
Jun 4, 2026
…om gate) (#371) ## Why PR #359 fixed the enforcer's score-**reading** bug (read `.score` from scorecard's JSON output instead of the absent SARIF field), but in doing so it reintroduced the job-**structure** bug that #304 had deliberately removed: it placed `ossf/scorecard-action` and a `run:` step in the same `check-score` job. OSSF's publish path requires scorecard-action jobs to be `uses:`-only, and hypatia flags this as `scorecard_publish_with_run_step` (high) — which is exactly what the post-merge scan on #359 caught. ## What Split the score path into two jobs: - **`compute-score`** — `uses:`-only: `checkout` → `scorecard-action` (JSON mode, `publish_results: false`, so no publish + no OIDC) → `upload-artifact` (`results.json`). - **`check-score`** — `download-artifact` → the `jq .score` gate. No scorecard-action. This mirrors the publish/gate split #304 established for the SARIF path. Verified every scorecard-action job is now `uses:`-only: | job | scorecard-action | run-step | |---|---|---| | `scorecard` (publish) | ✅ | — | | `compute-score` | ✅ | — | | `check-score` | — | ✅ | | `check-critical` | — | ✅ | `MIN_SCORE=5` and the publish `scorecard` job are unchanged — posture untouched. As before, this makes the gate **accurate**, not automatically green (a real score < 5 will correctly fail). ## Guardrail No `LICENSE` file or SPDX header touched. Workflow logic only. https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82 --- _Generated by [Claude Code](https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status after merging
main(2026-06-04)mainadvanced while this PR was open with a large workflow-normalization sweep that independently fixed 5 of the 6 workflows this PR originally targeted (boj-build,instant-sync,mirror-reusable,rust-ci-reusable,elixir-ci-reusable) via the same root-cause fixes (thesecrets→envmapping and thedetect-job pattern). On merge I resolved the conflicts by taking main's canonical versions of those 5 — they're equal-or-better (main'sboj-buildalso splits outvalidate-contractilesand reads the contractile set fromINDEX.a2mlinstead of hard-coding it).This PR now reduces to a single unique change:
scorecard-enforcer.yml.The remaining fix —
OpenSSF Scorecard Enforcerread the score from the wrong placecheck-scorereadjq '.runs[0].tool.driver.properties.score'from the SARIF, which doesn't carry the aggregate score →// 0→ the gate read 0 and failedMIN_SCORE=5on every push, never measuring real posture. Logs from the 2026-06-03mainrun confirm: thescorecardjob + all its steps succeed; onlycheck-scorefails at "Score: 0".Fix:
check-scorenow runs scorecard-action withresults_format: json+publish_results: false(so the job needs no OIDC/id-token) and reads.score. Thescorecardjob is unchanged and staysuses:-only (OSSF publish contract); its now-unused SARIF persist step is removed.MIN_SCORE=5is unchanged — posture policy untouched.This makes the gate accurate, not automatically green — if the repo's real score is < 5 it will (correctly) fail, which is then a genuine posture finding, not a workflow bug.
Not in this PR — needs you
Scorecards supply-chain security(scorecard.yml)startup_failure— distinct from the enforcer. It's a thin caller ofscorecard-reusable@3f34549c; the equivalent inline scorecard logic (inscorecard-enforcer) runs fine, so the failure is in the reusable-call / OIDC path (not retrievable from the Actions API — 0 jobs, no logs). It also looks redundant withscorecard-enforcer.yml. Recommend either retiringscorecard.ymlor reading the run annotation in the UI — likely an OIDC/admin-settings issue (cf. #352/#353).Guardrail
No
LICENSEfile or SPDX header touched. Workflow logic only.https://claude.ai/code/session_011xv3VLrqeXkpjXxUojKz82