feat(cicd_rules): scorecard_wrapper_missing_job_permissions (#390)#404
Merged
Conversation
Detect `.github/workflows/scorecard.yml` wrappers that call the standards scorecard-reusable.yml but omit `security-events: write` on the calling job. Called-workflow permissions are capped by the caller, so ossf/scorecard-action cannot upload its SARIF and the scheduled Scorecard run fails with `startup_failure` (silent CI failure, no logs). Estate baseline 2026-05-30: 37 affected wrappers (35 unique + 2 nested copies). - CicdRules.scan_scorecard_wrapper_permissions/2 walks the repo (root + nested monorepo copies); pure CicdRules.check_scorecard_wrapper_permissions/2 - opts[:path_allow_prefixes] carve-out for bespoke inline scorecard.yml - finding carries severity/file/reason + a recommended fix snippet - surfaced through the Hypatia.Rules facade via defdelegate - test/rules/cicd_rules_scorecard_wrapper_test.exs: sensitivity (positive + nested) and specificity (perm present, no-reusable, carve-out, spacing) - CHANGELOG.md + CHANGELOG.adoc entries https://claude.ai/code/session_01J8oLNn6MjKDRRUF65e2jLf
hyperpolymath
pushed a commit
that referenced
this pull request
May 30, 2026
#403 WF018) PR #404 added scorecard_wrapper_missing_job_permissions to cicd_rules.ex, but PR #403 had concurrently implemented the same #390 detection as WF018 (check_scorecard_wrapper_missing_job_permissions) in workflow_audit.ex — its canonical home alongside WF019/WF020. This removes the redundant cicd_rules copy (rule, facade delegate, test, changelog entries); WF018 stays as the single implementation. Verified locally (Elixir 1.14): cicd_rules.ex compiles with zero warnings; format-isolation confirms a pure deletion with no pre-existing reformat. https://claude.ai/code/session_01J8oLNn6MjKDRRUF65e2jLf
hyperpolymath
added a commit
that referenced
this pull request
May 30, 2026
…F018) (#407) PR #404 added scorecard_wrapper_missing_job_permissions to cicd_rules.ex, but #403 had concurrently implemented the same #390 detection as WF018 in workflow_audit.ex. Removes the redundant cicd_rules copy (rule, facade delegate, test, changelog); WF018 remains the single implementation. The #362 cron rule and #405 nodejs carve-outs are untouched. Verified locally (Elixir 1.14): zero-warning compile; format-isolation shows a pure deletion. https://claude.ai/code/session_01J8oLNn6MjKDRRUF65e2jLf
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.
What
Implements #390 — a new
Hypatia.Rules.CicdRulesdetector,scorecard_wrapper_missing_job_permissions, for a silent-CI-failure class across the estate.A
.github/workflows/scorecard.ymlthat calls the standardsscorecard-reusable.ymlmust grantsecurity-events: write(andid-token: write) on the calling job. Called-workflow permissions are capped by the caller, so a wrapper that omits the grant leavesossf/scorecard-actionunable to upload its SARIF — every scheduled Scorecard run then fails withstartup_failureand no logs. Estate baseline 2026-05-30: 37 affected wrappers (35 unique + 2 inert nested-monorepo copies). Prior art:julia-professional-registry#19,absolute-zero#68.How
CicdRules.scan_scorecard_wrapper_permissions/2— walksrepo_pathfor every.github/workflows/scorecard.yml(root and nested monorepo copies) and emits a finding when the file referencesscorecard-reusable.ymland does not match~r/security-events:\s*write/.CicdRules.check_scorecard_wrapper_permissions/2— pure(path, content) -> {:fail, finding} | :okpredicate behind the scan.opts[:path_allow_prefixes]— substring carve-out for bespoke inline scorecard workflows that manage their own permissions shape. (Inline scorecard.yml that doesn't call the reusable is already ignored by construction.)%{rule:, severity: :high, file:, reason:, fix:}— thefixis a ready-to-pastepermissions:block.Hypatia.Rulesfacade viadefdelegate.CHANGELOG.md+CHANGELOG.adocentries.Acceptance criteria (#390)
scorecard.ymlsatisfying the criterionfix:)path_allow_prefixescarve-out for inline (non-reusable) scorecard.ymlTests
test/rules/cicd_rules_scorecard_wrapper_test.exs— sensitivity (positive + nested copy) and specificity (perm present, no-reusable, carve-out, irregular spacing), plus pure-predicate unit tests.Honesty / CI note
deps, no_build), so I could not runmix test/mix format --check-formattedlocally. The code follows the module's established conventions (single-line attribute strings; pipe-chain + nested-casemirroring the existingscan_content_patternsengine; ≤98-col lines). Please let CI run the suite — I'm flagging this rather than claiming green.scorecard.ymlalready grants the perm, so it would be silent on this repo regardless.Closes #390
Generated by Claude Code