Skip to content

Commit 64aa1a1

Browse files
revert(cicd_rules): drop duplicate scorecard rule (#390 dup of #403 WF018) (#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
1 parent afabba4 commit 64aa1a1

5 files changed

Lines changed: 0 additions & 250 deletions

File tree

CHANGELOG.adoc

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,6 @@ Surfaced through the facade as
2929
(same-day-of-week + daily-subset oracles) and specificity (different-time
3030
non-subset, single cron, commented-out lines).
3131

32-
==== `CicdRules` rule `scorecard_wrapper_missing_job_permissions` (2026-05-30, #390)
33-
34-
New forward-detection rule for a silent-CI-failure class: a
35-
`.github/workflows/scorecard.yml` that *calls* the standards
36-
`scorecard-reusable.yml` but omits `security-events: write` on the calling
37-
job. Called-workflow permissions are capped by the caller, so
38-
`ossf/scorecard-action` cannot upload its SARIF and every scheduled
39-
Scorecard run fails with `startup_failure` — no logs. Estate baseline
40-
2026-05-30: 37 affected wrappers (35 unique + 2 inert nested-monorepo
41-
copies). Prior art: `julia-professional-registry#19`, `absolute-zero#68`.
42-
43-
Surfaced through the facade as
44-
`Hypatia.Rules.scan_scorecard_wrapper_permissions/2`; the pure predicate
45-
`CicdRules.check_scorecard_wrapper_permissions/2` and an
46-
`opts[:path_allow_prefixes]` carve-out (for bespoke inline scorecard
47-
workflows) are covered by `test/rules/cicd_rules_scorecard_wrapper_test.exs`
48-
for both sensitivity (positive + nested copy) and specificity (perm present,
49-
no-reusable, carve-out).
50-
5132
==== `WorkflowAudit` rules WF014–WF017 (2026-05-30, PRs #393 + #396)
5233

5334
Four new forward-detection rules surfacing patterns root-fixed in

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
### Added
2222

2323
- feat(rules): CicdRules `duplicate_cron_schedule` — flag workflows with redundant cron entries on the same day-of-week / daily-subset (#362)
24-
- feat(rules): CicdRules `scorecard_wrapper_missing_job_permissions` — flag scorecard.yml wrappers that call the standards reusable but omit `security-events: write` on the calling job (#390)
2524
- feat(rules): AffineScript hand-port pitfalls — HANDLE-as-fn-name + OCaml float ops (#332)
2625
- feat(rules): wire 4 new rule modules through the facade (#326)
2726
- feat(rules): ResearchExtensions (RE001-RE010) — 10 rules from Snyk/StepSecurity/Endor/academic literature (#325)

lib/rules/cicd_rules.ex

Lines changed: 0 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -871,102 +871,6 @@ defmodule Hypatia.Rules.CicdRules do
871871
end
872872
end
873873

874-
# ---------------------------------------------------------------------------
875-
# Scorecard Wrapper Permissions (#390)
876-
# ---------------------------------------------------------------------------
877-
#
878-
# A `.github/workflows/scorecard.yml` that *calls* the standards
879-
# `scorecard-reusable.yml` MUST grant `security-events: write` (and
880-
# `id-token: write`) on the calling job. Called-workflow permissions are
881-
# CAPPED by the caller, so a wrapper that omits the grant leaves
882-
# `ossf/scorecard-action` unable to upload its SARIF — every scheduled
883-
# Scorecard run then fails with `startup_failure` and *no logs*. That
884-
# silent-CI-failure shape is exactly what Hypatia exists to catch.
885-
#
886-
# Estate baseline 2026-05-30: 37 affected wrappers (35 unique + 2 inert
887-
# nested-monorepo copies). Prior art: julia-professional-registry#19,
888-
# absolute-zero#68 (memory: feedback_scorecard_wrapper_caller_permissions).
889-
890-
@scorecard_wrapper_path ".github/workflows/scorecard.yml"
891-
@scorecard_reusable_marker "scorecard-reusable.yml"
892-
@scorecard_required_perm ~r/security-events:\s*write/
893-
@scorecard_missing_perm_reason "scorecard.yml calls the standards scorecard-reusable.yml but does not grant `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)."
894-
@scorecard_missing_perm_fix "Grant the calling job `security-events: write` (and `id-token: write`); the reusable re-asserts them, but the caller caps them:\n permissions:\n security-events: write\n id-token: write"
895-
896-
@doc """
897-
Scan `repo_path` for scorecard wrappers that call the standards reusable
898-
but omit the required `security-events: write` job permission (#390).
899-
900-
A finding is emitted for each `.github/workflows/scorecard.yml` (repo-root
901-
*or* nested monorepo copy) that, in the same file:
902-
903-
* references `scorecard-reusable.yml` (i.e. uses the reusable), AND
904-
* does NOT grant `security-events: write`.
905-
906-
Inline scorecard workflows that do not call the reusable are ignored by
907-
construction (the first condition fails). `opts[:path_allow_prefixes]` is a
908-
list of substrings; any wrapper whose relative path contains one is skipped
909-
— an explicit carve-out for bespoke scorecard workflows that manage their
910-
own permissions shape.
911-
912-
Returns `[%{rule:, severity:, file:, reason:, fix:}]`.
913-
"""
914-
def scan_scorecard_wrapper_permissions(repo_path, opts \\ []) do
915-
allow_prefixes = Keyword.get(opts, :path_allow_prefixes, [])
916-
917-
Path.wildcard("#{repo_path}/**/*", match_dot: true)
918-
|> Enum.reject(&File.dir?/1)
919-
|> Enum.map(&Path.relative_to(&1, repo_path))
920-
|> Enum.filter(fn rel ->
921-
not String.starts_with?(rel, ".git/") and scorecard_wrapper_path?(rel)
922-
end)
923-
|> Enum.reject(fn rel -> Enum.any?(allow_prefixes, &String.contains?(rel, &1)) end)
924-
|> Enum.flat_map(fn rel ->
925-
case File.read(Path.join(repo_path, rel)) do
926-
{:ok, content} ->
927-
case check_scorecard_wrapper_permissions(rel, content) do
928-
{:fail, finding} -> [finding]
929-
:ok -> []
930-
end
931-
932-
{:error, _} ->
933-
[]
934-
end
935-
end)
936-
end
937-
938-
@doc """
939-
Pure predicate behind `scan_scorecard_wrapper_permissions/2`.
940-
941-
Given a scorecard wrapper's relative `path` and its `content`, returns
942-
`{:fail, finding}` when the file calls the standards reusable but does not
943-
grant `security-events: write`, or `:ok` otherwise.
944-
"""
945-
def check_scorecard_wrapper_permissions(path, content) do
946-
uses_reusable? = String.contains?(content, @scorecard_reusable_marker)
947-
grants_perm? = Regex.match?(@scorecard_required_perm, content)
948-
949-
if uses_reusable? and not grants_perm? do
950-
finding = %{
951-
rule: :scorecard_wrapper_missing_job_permissions,
952-
severity: :high,
953-
file: path,
954-
reason: @scorecard_missing_perm_reason,
955-
fix: @scorecard_missing_perm_fix
956-
}
957-
958-
{:fail, finding}
959-
else
960-
:ok
961-
end
962-
end
963-
964-
# True when `rel` is a scorecard wrapper workflow — the repo-root copy or
965-
# any nested monorepo copy (`pkg/.github/workflows/scorecard.yml`).
966-
defp scorecard_wrapper_path?(rel) do
967-
rel == @scorecard_wrapper_path or String.ends_with?(rel, "/" <> @scorecard_wrapper_path)
968-
end
969-
970874
# ---------------------------------------------------------------------------
971875
# Duplicate cron schedules (#362)
972876
# ---------------------------------------------------------------------------

lib/rules/rules.ex

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -422,12 +422,6 @@ defmodule Hypatia.Rules do
422422
"""
423423
defdelegate detect_waste(repo_info), to: CicdRules
424424

425-
@doc """
426-
Scan for scorecard wrappers that call the standards reusable but omit the
427-
required `security-events: write` job permission (#390).
428-
"""
429-
defdelegate scan_scorecard_wrapper_permissions(repo_path, opts \\ []), to: CicdRules
430-
431425
@doc """
432426
Scan workflows for redundant `cron:` schedules firing on the same
433427
day-of-week (#362).

test/rules/cicd_rules_scorecard_wrapper_test.exs

Lines changed: 0 additions & 128 deletions
This file was deleted.

0 commit comments

Comments
 (0)