From de2050e0c22ec91c3e765b01be15a3654f95d80d Mon Sep 17 00:00:00 2001 From: toiroakr <3090700+toiroakr@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:35:47 +0000 Subject: [PATCH 1/6] chore(upstream): refresh zizmor 1.28.0 --- fixtures/upstream/zizmor/UPSTREAM.txt | 2 +- .../integration/audit/dependabot_cooldown.rs | 40 ++---- .../tests/integration/audit/github_env.rs | 18 +++ .../integration/audit/template_injection.rs | 72 ++++++----- .../upstream/zizmor/tests/integration/cli.rs | 2 +- .../upstream/zizmor/tests/integration/e2e.rs | 36 +++++- .../zizmor/tests/integration/e2e/json_v1.rs | 15 +++ ...ation__e2e__crater__pyca_cryptography.snap | 22 +--- .../integration__e2e__crater__warehouse.snap | 9 +- .../integration__e2e__json_v1__json_v1.snap | 27 ++-- ...n__e2e__json_v1__json_v1_fix_metadata.snap | 116 ++++++++++++++++++ .../test-data/github-env/issue-2200-repro.yml | 23 ++++ .../template-injection/issue-2197-repro.yml | 20 +++ mise.toml | 2 +- pr-body.md | 27 ++-- refresh-summary.json | 12 +- 16 files changed, 327 insertions(+), 116 deletions(-) create mode 100644 fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1_fix_metadata.snap create mode 100644 fixtures/upstream/zizmor/tests/integration/test-data/github-env/issue-2200-repro.yml create mode 100644 fixtures/upstream/zizmor/tests/integration/test-data/template-injection/issue-2197-repro.yml diff --git a/fixtures/upstream/zizmor/UPSTREAM.txt b/fixtures/upstream/zizmor/UPSTREAM.txt index ebfbdc2..43613bb 100644 --- a/fixtures/upstream/zizmor/UPSTREAM.txt +++ b/fixtures/upstream/zizmor/UPSTREAM.txt @@ -1,2 +1,2 @@ -zizmorcore/zizmor@v1.27.0 +zizmorcore/zizmor@v1.28.0 vendored: crates/zizmor/tests -> tests diff --git a/fixtures/upstream/zizmor/tests/integration/audit/dependabot_cooldown.rs b/fixtures/upstream/zizmor/tests/integration/audit/dependabot_cooldown.rs index 0a7dc5c..905b611 100644 --- a/fixtures/upstream/zizmor/tests/integration/audit/dependabot_cooldown.rs +++ b/fixtures/upstream/zizmor/tests/integration/audit/dependabot_cooldown.rs @@ -15,7 +15,7 @@ fn test_missing_cooldown() -> anyhow::Result<()> { --> @@INPUT@@:4:5 | 4 | - package-ecosystem: pip - | ^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration + | ^^^^^^^^^^^^^^^^^^^^^^ insufficient implicit default-days (less than 7) | = note: audit confidence → High = note: this finding has an auto-fix @@ -40,7 +40,7 @@ fn test_no_default_days() -> anyhow::Result<()> { --> @@INPUT@@:6:5 | 6 | cooldown: {} - | ^^^^^^^^^^^^ no default-days configured + | ^^^^^^^^^^^^ insufficient implicit default-days (less than 7) | = note: audit confidence → High = note: this finding has an auto-fix @@ -60,16 +60,16 @@ fn test_default_days_too_short() -> anyhow::Result<()> { )) .run()?, @" - help[dependabot-cooldown]: insufficient cooldown in Dependabot updates + warning[dependabot-cooldown]: insufficient cooldown in Dependabot updates --> @@INPUT@@:7:7 | 7 | default-days: 2 | ^^^^^^^^^^^^^^^ insufficient default-days configured (less than 7) | - = note: audit confidence → Medium + = note: audit confidence → High = note: this finding has an auto-fix - 1 findings (1 safe fixes): 0 informational, 1 low, 0 medium, 0 high + 1 findings (1 safe fixes): 0 informational, 0 low, 1 medium, 0 high "); Ok(()) @@ -180,33 +180,7 @@ fn test_multi_ecosystem_group_with_cooldown() -> anyhow::Result<()> { )) .args(["--pedantic"]) .run()?, - @" - help[dependabot-cooldown]: insufficient cooldown in Dependabot updates - --> @@INPUT@@:13:5 - | - 10 | multi-ecosystem-group: all - | --------------------- multi-ecosystem-group configured here - ... - 13 | / cooldown: - 14 | | default-days: 7 - | |_____________________^ multi-ecosystem-group cooldowns do not batch updates correctly - | - = note: audit confidence → High - - help[dependabot-cooldown]: insufficient cooldown in Dependabot updates - --> @@INPUT@@:20:5 - | - 17 | multi-ecosystem-group: all - | --------------------- multi-ecosystem-group configured here - ... - 20 | / cooldown: - 21 | | default-days: 7 - | |______________________^ multi-ecosystem-group cooldowns do not batch updates correctly - | - = note: audit confidence → High - - 2 findings: 0 informational, 2 low, 0 medium, 0 high - " + @"No findings to report. Good job!" ); Ok(()) @@ -223,7 +197,7 @@ fn test_opentofu_cooldown() -> anyhow::Result<()> { --> @@INPUT@@:5:5 | 5 | - package-ecosystem: opentofu - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ insufficient implicit default-days (less than 7) | = note: audit confidence → High = note: this finding has an auto-fix diff --git a/fixtures/upstream/zizmor/tests/integration/audit/github_env.rs b/fixtures/upstream/zizmor/tests/integration/audit/github_env.rs index 4760414..b855267 100644 --- a/fixtures/upstream/zizmor/tests/integration/audit/github_env.rs +++ b/fixtures/upstream/zizmor/tests/integration/audit/github_env.rs @@ -108,3 +108,21 @@ fn test_issue_1333() -> Result<()> { Ok(()) } + +/// Reproducer for #2200. Insertions into `GITHUB_ENV` should not be flagged if they are +/// entirely literal, i.e. have no variable expansions within them. +/// +/// See: +#[test] +fn test_issue_2200() -> Result<()> { + insta::assert_snapshot!( + zizmor() + .output(OutputMode::Both) + .setenv("RUST_LOG", "warn") + .input(input_under_test("github-env/issue-2200-repro.yml")) + .run()?, + @"No findings to report. Good job! (1 ignored)" + ); + + Ok(()) +} diff --git a/fixtures/upstream/zizmor/tests/integration/audit/template_injection.rs b/fixtures/upstream/zizmor/tests/integration/audit/template_injection.rs index 242a588..3e86da5 100644 --- a/fixtures/upstream/zizmor/tests/integration/audit/template_injection.rs +++ b/fixtures/upstream/zizmor/tests/integration/audit/template_injection.rs @@ -616,34 +616,6 @@ fn test_issue_1664() -> Result<()> { Ok(()) } -/// Repro case for #1903: parenthesized compound expressions in context -/// position (e.g. `(a || b).foo`) should not crash zizmor. -/// -/// See: -#[test] -fn test_issue_1903() -> Result<()> { - insta::assert_snapshot!( - zizmor() - .input(input_under_test("template-injection/issue-1903-repro.yml")) - .run()?, - @r#" - info[template-injection]: code injection via template expansion - --> @@INPUT@@:21:24 - | - 21 | - run: echo "${{ (github.event.pull_request || github.event.issue).number }}" - | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code - | | - | this run block - | - = note: audit confidence → Low - - 1 finding: 1 informational, 0 low, 0 medium, 0 high - "# - ); - - Ok(()) -} - /// Repro case for #1802: `needs.*.result` expressions should not be considered injection risks in the default persona. /// /// See: @@ -700,3 +672,47 @@ fn test_issue_1802() -> Result<()> { Ok(()) } + +/// Repro case for #1903: parenthesized compound expressions in context +/// position (e.g. `(a || b).foo`) should not crash zizmor. +/// +/// See: +#[test] +fn test_issue_1903() -> Result<()> { + insta::assert_snapshot!( + zizmor() + .input(input_under_test("template-injection/issue-1903-repro.yml")) + .run()?, + @r#" + info[template-injection]: code injection via template expansion + --> @@INPUT@@:21:24 + | + 21 | - run: echo "${{ (github.event.pull_request || github.event.issue).number }}" + | --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code + | | + | this run block + | + = note: audit confidence → Low + + 1 finding: 1 informational, 0 low, 0 medium, 0 high + "# + ); + + Ok(()) +} + +/// Repro case for #2197: `steps.*.outcome` contexts should not be considered injection +/// risks in the default persona. +/// +/// See: +#[test] +fn test_issue_2197() -> Result<()> { + insta::assert_snapshot!( + zizmor() + .input(input_under_test("template-injection/issue-2197-repro.yml")) + .run()?, + @"No findings to report. Good job! (1 suppressed)" + ); + + Ok(()) +} diff --git a/fixtures/upstream/zizmor/tests/integration/cli.rs b/fixtures/upstream/zizmor/tests/integration/cli.rs index dfe9fe0..afa7797 100644 --- a/fixtures/upstream/zizmor/tests/integration/cli.rs +++ b/fixtures/upstream/zizmor/tests/integration/cli.rs @@ -115,7 +115,7 @@ updates: --> :3:5 | 3 | - package-ecosystem: github-actions - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ insufficient implicit default-days (less than 7) | = note: audit confidence → High = note: this finding has an auto-fix diff --git a/fixtures/upstream/zizmor/tests/integration/e2e.rs b/fixtures/upstream/zizmor/tests/integration/e2e.rs index 26dcd2e..c8dcc99 100644 --- a/fixtures/upstream/zizmor/tests/integration/e2e.rs +++ b/fixtures/upstream/zizmor/tests/integration/e2e.rs @@ -615,7 +615,7 @@ fn issue_1300() -> Result<()> { Caused by: 0: input @@INPUT@@ doesn't contain any workflows 1: request error while accessing GitHub API - 2: HTTP status client error (404 Not Found) for url (https://api.github.com/repos/@@INPUT@@/contents/.github/workflows) + 2: HTTP status client error (404 Not Found) for url (https://api.github.com/repos/@@INPUT@@/contents/.github/workflows?ref=HEAD) " ); @@ -855,3 +855,37 @@ fn issue_2182() -> Result<()> { Ok(()) } + +/// Regression test for #2202. +/// +/// Ensures that we produce a reasonable error message when the user +/// asks us to audit an ambiguous remote input (one where the @ +/// is both a branch and a tag). +#[cfg_attr(not(feature = "gh-token-tests"), ignore)] +#[test] +fn issue_2202() -> Result<()> { + insta::assert_snapshot!( + zizmor() + .offline(NetworkMode::AssertOnline) + .output(OutputMode::Stderr) + .expects_failure(1) + .input( + "woodruffw-experiments/zizmor-issue-2202@v1" + ) + .run()?, + @r#" + INFO zizmor: 🌈 zizmor v@@VERSION@@ + fatal: no audit was performed + error: remote input has an ambiguous Git reference ("v1" is both a tag and a branch) + | + = help: disambiguate the Git ref by putting it in the right namespace + = help: example: woodruffw-experiments/zizmor-issue-2202@refs/heads/v1 + = help: example: woodruffw-experiments/zizmor-issue-2202@refs/tags/v1 + + Caused by: + remote input has an ambiguous Git reference ("v1" is both a tag and a branch) + "# + ); + + Ok(()) +} diff --git a/fixtures/upstream/zizmor/tests/integration/e2e/json_v1.rs b/fixtures/upstream/zizmor/tests/integration/e2e/json_v1.rs index 1a8b814..fc7dc47 100644 --- a/fixtures/upstream/zizmor/tests/integration/e2e/json_v1.rs +++ b/fixtures/upstream/zizmor/tests/integration/e2e/json_v1.rs @@ -16,3 +16,18 @@ fn test_json_v1() { assert_snapshot!(output); } + +#[test] +fn test_json_v1_fix_metadata() { + // A finding with an available fix exposes symbolic fix metadata + // (title, key, disposition) in its `fixes` array. + let output = zizmor() + .args(["--format=json-v1"]) + .input(input_under_test( + "dependabot-execution/basic/dependabot.yml", + )) + .run() + .expect("Failed to run zizmor with JSON v1 format"); + + assert_snapshot!(output); +} diff --git a/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__pyca_cryptography.snap b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__pyca_cryptography.snap index aaacd1d..ff6db5c 100644 --- a/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__pyca_cryptography.snap +++ b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__pyca_cryptography.snap @@ -1,6 +1,6 @@ --- source: crates/zizmor/tests/integration/e2e/crater.rs -expression: "zizmor().offline(false).output(OutputMode::Both).args([\"--persona=pedantic\"]).input(\"pyca/cryptography@43eb178ee3aae8d0060221118437b03c23570a41\").run()?" +expression: "zizmor().offline(NetworkMode::AssertOnline).output(OutputMode::Both).args([\"--persona=pedantic\"]).input(\"pyca/cryptography@43eb178ee3aae8d0060221118437b03c23570a41\").run()?" --- INFO zizmor: 🌈 zizmor v@@VERSION@@ INFO collect_inputs: zizmor::registry::input: collected 15 inputs from pyca/cryptography @@ -83,24 +83,6 @@ error[template-injection]: code injection via template expansion = note: audit confidence → High = note: this finding has an auto-fix -warning[dependabot-cooldown]: insufficient cooldown in Dependabot updates - --> .github/dependabot.yml:17:5 - | -17 | - package-ecosystem: cargo - | ^^^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration - | - = note: audit confidence → High - = note: this finding has an auto-fix - -warning[dependabot-cooldown]: insufficient cooldown in Dependabot updates - --> .github/dependabot.yml:28:5 - | -28 | - package-ecosystem: uv - | ^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration - | - = note: audit confidence → High - = note: this finding has an auto-fix - warning[excessive-permissions]: overly broad permissions --> .github/workflows/auto-close-stale.yml:1:1 | @@ -789,4 +771,4 @@ info[superfluous-actions]: action functionality is already included by the runne | = note: audit confidence → Low -80 findings (9 suppressed): 19 informational, 35 low, 7 medium, 10 high +78 findings (9 suppressed): 19 informational, 35 low, 5 medium, 10 high diff --git a/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__warehouse.snap b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__warehouse.snap index 125dde2..b4d4ea0 100644 --- a/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__warehouse.snap +++ b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__crater__warehouse.snap @@ -1,6 +1,6 @@ --- source: crates/zizmor/tests/integration/e2e/crater.rs -expression: "zizmor().offline(false).output(OutputMode::Both).args([\"--persona=pedantic\"]).input(\"pypi/warehouse@9ed30d191788fcfa9c5be56bcce9b743e758903e\").run()?" +expression: "zizmor().offline(NetworkMode::AssertOnline).output(OutputMode::Both).args([\"--persona=pedantic\"]).input(\"pypi/warehouse@9ed30d191788fcfa9c5be56bcce9b743e758903e\").run()?" --- INFO zizmor: 🌈 zizmor v@@VERSION@@ INFO collect_inputs: zizmor::registry::input: collected 8 inputs from pypi/warehouse @@ -16,7 +16,7 @@ warning[dependabot-cooldown]: insufficient cooldown in Dependabot updates --> .github/dependabot.yml:4:3 | 4 | - package-ecosystem: pip - | ^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration + | ^^^^^^^^^^^^^^^^^^^^^^ insufficient implicit default-days (less than 7) | = note: audit confidence → High = note: this finding has an auto-fix @@ -25,7 +25,7 @@ warning[dependabot-cooldown]: insufficient cooldown in Dependabot updates --> .github/dependabot.yml:29:3 | 29 | - package-ecosystem: github-actions - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ insufficient implicit default-days (less than 7) | = note: audit confidence → High = note: this finding has an auto-fix @@ -34,7 +34,7 @@ warning[dependabot-cooldown]: insufficient cooldown in Dependabot updates --> .github/dependabot.yml:33:3 | 33 | - package-ecosystem: docker - | ^^^^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration + | ^^^^^^^^^^^^^^^^^^^^^^^^^ insufficient implicit default-days (less than 7) | = note: audit confidence → High = note: this finding has an auto-fix @@ -357,4 +357,3 @@ help[concurrency-limits]: insufficient job-level concurrency limits = note: audit confidence → High 34 findings (2 suppressed): 4 informational, 14 low, 8 medium, 6 high - diff --git a/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1.snap b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1.snap index 5f2da1e..948ede3 100644 --- a/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1.snap +++ b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1.snap @@ -224,7 +224,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unpinned-uses", @@ -293,7 +294,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unpinned-uses", @@ -362,7 +364,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unpinned-uses", @@ -431,7 +434,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unsound-contains", @@ -493,7 +497,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unsound-contains", @@ -555,7 +560,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unsound-contains", @@ -617,7 +623,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unsound-contains", @@ -679,7 +686,8 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] }, { "ident": "unsound-contains", @@ -741,6 +749,7 @@ expression: output } } ], - "ignored": false + "ignored": false, + "fixes": [] } ] diff --git a/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1_fix_metadata.snap b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1_fix_metadata.snap new file mode 100644 index 0000000..ea8643b --- /dev/null +++ b/fixtures/upstream/zizmor/tests/integration/e2e/snapshots/integration__e2e__json_v1__json_v1_fix_metadata.snap @@ -0,0 +1,116 @@ +--- +source: crates/zizmor/tests/integration/e2e/json_v1.rs +expression: output +--- +[ + { + "ident": "dependabot-execution", + "desc": "external code execution in Dependabot updates", + "url": "https://docs.zizmor.sh/audits/#dependabot-execution", + "determinations": { + "confidence": "High", + "severity": "High", + "persona": "Regular" + }, + "locations": [ + { + "symbolic": { + "key": { + "Local": { + "verbatim_path": "@@INPUT@@" + } + }, + "annotation": "enabled here", + "route": { + "route": [ + { + "Key": "updates" + }, + { + "Index": 0 + }, + { + "Key": "insecure-external-code-execution" + } + ] + }, + "feature_kind": "Normal", + "kind": "Primary" + }, + "concrete": { + "location": { + "start_point": { + "row": 9, + "column": 4 + }, + "end_point": { + "row": 9, + "column": 43 + }, + "offset_span": { + "start": 141, + "end": 180 + } + }, + "feature": " insecure-external-code-execution: allow", + "comments": [] + } + }, + { + "symbolic": { + "key": { + "Local": { + "verbatim_path": "@@INPUT@@" + } + }, + "annotation": "this ecosystem", + "route": { + "route": [ + { + "Key": "updates" + }, + { + "Index": 0 + }, + { + "Key": "package-ecosystem" + } + ] + }, + "feature_kind": "Normal", + "kind": "Related" + }, + "concrete": { + "location": { + "start_point": { + "row": 3, + "column": 4 + }, + "end_point": { + "row": 3, + "column": 26 + }, + "offset_span": { + "start": 25, + "end": 47 + } + }, + "feature": "package-ecosystem: pip", + "comments": [] + } + } + ], + "ignored": false, + "fixes": [ + { + "title": "set insecure-external-code-execution to deny", + "key": { + "Local": { + "verbatim_path": "@@INPUT@@" + } + }, + "disposition": "unsafe" + } + ] + } +] diff --git a/fixtures/upstream/zizmor/tests/integration/test-data/github-env/issue-2200-repro.yml b/fixtures/upstream/zizmor/tests/integration/test-data/github-env/issue-2200-repro.yml new file mode 100644 index 0000000..e697be9 --- /dev/null +++ b/fixtures/upstream/zizmor/tests/integration/test-data/github-env/issue-2200-repro.yml @@ -0,0 +1,23 @@ +# See: https://github.com/zizmorcore/zizmor/issues/2200 +# +# Inserting literal values (with no variable interpolations) into `GITHUB_ENV` +# should not be flagged. + +name: issue-2200 + +on: pull_request_target # zizmor: ignore[dangerous-triggers] + +permissions: {} + +concurrency: + group: issue-2200 + cancel-in-progress: true + +jobs: + repro: + name: Repro + runs-on: ubuntu-latest + steps: + - run: | + printf "BUNDLE_WITH=avo\nRAILS_GROUPS=avo\n" >> "$GITHUB_ENV" + shell: bash diff --git a/fixtures/upstream/zizmor/tests/integration/test-data/template-injection/issue-2197-repro.yml b/fixtures/upstream/zizmor/tests/integration/test-data/template-injection/issue-2197-repro.yml new file mode 100644 index 0000000..6281eb3 --- /dev/null +++ b/fixtures/upstream/zizmor/tests/integration/test-data/template-injection/issue-2197-repro.yml @@ -0,0 +1,20 @@ +# See: https://github.com/zizmorcore/zizmor/issues/2197 +# +# `steps.*.outcome` context accesses should not be considered an injection risk. + +name: issue-2197-repro + +on: issue_comment + +permissions: {} + +concurrency: + group: issue-2197-repro + cancel-in-progress: true + +jobs: + repro: + name: Repro + runs-on: ubuntu-latest + steps: + - run: echo "${{ steps.fake.outcome }}" diff --git a/mise.toml b/mise.toml index 6c689b8..6880745 100644 --- a/mise.toml +++ b/mise.toml @@ -13,5 +13,5 @@ [tools] "aqua:rhysd/actionlint" = "1.7.12" -"ubi:zizmorcore/zizmor" = "1.27.0" +"ubi:zizmorcore/zizmor" = "1.28.0" "aqua:suzuki-shunsuke/ghalint" = "1.5.6" diff --git a/pr-body.md b/pr-body.md index 3c42b34..ec9a1ad 100644 --- a/pr-body.md +++ b/pr-body.md @@ -2,30 +2,35 @@ Refresh upstream linter pins (auto-generated). | tool | from | to | release | compare | | --- | --- | --- | --- | --- | -| zizmor | 1.26.1 | 1.27.0 | [v1.27.0](https://github.com/zizmorcore/zizmor/releases/tag/v1.27.0) | [`v1.26.1...v1.27.0`](https://github.com/zizmorcore/zizmor/compare/v1.26.1...v1.27.0) | +| zizmor | 1.27.0 | 1.28.0 | [v1.28.0](https://github.com/zizmorcore/zizmor/releases/tag/v1.28.0) | [`v1.27.0...v1.28.0`](https://github.com/zizmorcore/zizmor/compare/v1.27.0...v1.28.0) | Fixtures under `fixtures/upstream//` were re-vendored from the new tag. This PR is opened with `GITHUB_TOKEN`, so `upstream-parity` does **not** run automatically — add the `run-parity` label to trigger it. Its step summary then lists any divergences (`unmapped` / soft) that point at newly-added upstream checks. ## Upstream release notes -
zizmor v1.27.0 +
zizmor v1.28.0 -## New Features 🌈[🔗](https://docs.zizmor.sh/release-notes/#new-feartures) +## Security 🔒[🔗](https://docs.zizmor.sh/release-notes/#security) -- zizmor now has experimental support for workflows that specify parallel steps. See [Usage - Parallel steps](https://docs.zizmor.sh/usage/#parallel-steps) for more information ([#2153](https://github.com/zizmorcore/zizmor/issues/2153)) -Enhancements 🌱[🔗](https://docs.zizmor.sh/release-notes/#enhancements) +- v1.27.0 contained a logging defect that would print any configured GitHub credentials as part of zizmor's cleartext logging. No versions other than v1.27.0 were affected. See [GHSA-f42p-wjw5-97qh](https://github.com/zizmorcore/zizmor/security/advisories/GHSA-f42p-wjw5-97qh) for full information. -- zizmor's handling of paths is now more consistent, particularly when run on Windows ([#2163](https://github.com/zizmorcore/zizmor/issues/2163)) + Many thanks to [@shaanmajid](https://github.com/shaanmajid) for finding and reporting this vulnerability. -- zizmor now emits a helpful warning when being run in implicit offline mode ([#2180](https://github.com/zizmorcore/zizmor/issues/2180)) +## Enhancements 🌱[🔗](https://docs.zizmor.sh/release-notes/#enhancements) -## Bug Fixes 🐛[🔗](https://docs.zizmor.sh/release-notes/#bug-fixes) +- The JSON (v1) output format now includes metadata for each finding's fixes, if the finding has fixes ([#2186](https://github.com/zizmorcore/zizmor/issues/2186)) + +- The [dependabot-cooldown](https://docs.zizmor.sh/audits/#dependabot-cooldown) audit is now aware of GitHub's new three-day default cooldown ([#2193](https://github.com/zizmorcore/zizmor/issues/2193)) -- Fixed a bug where the [secrets-outside-env](https://docs.zizmor.sh/audits/#secrets-outside-env) audit would not honor ignore comments within the same job scope ([#2157](https://github.com/zizmorcore/zizmor/issues/2157)) +- sbt is now recognized as a package-ecosystem in dependabot.yml ([#2211](https://github.com/zizmorcore/zizmor/issues/2211)) -- Fixed a bug where the [ref-version-mismatch](https://docs.zizmor.sh/audits/#ref-version-mismatch) audit would not honor ignore comments within the same steps scope ([#2177](https://github.com/zizmorcore/zizmor/issues/2177)) +## Bug Fixes 🐛[🔗](https://docs.zizmor.sh/release-notes/#bug-fixes) -- Fixed a bug where `--collect=[MODE]` was not correctly handled when auditing remote inputs ([#2185](https://github.com/zizmorcore/zizmor/issues/2185)) +- Fixed a bug where the [template-injection](https://docs.zizmor.sh/audits/#template-injection) audit would incorrectly flag `steps.*.outcome` and `steps.*.conclusion` as injection risks in the default persona ([#2199](https://github.com/zizmorcore/zizmor/issues/2199)) + +- Fixed a bug where the [github-env](https://docs.zizmor.sh/audits/#github-env) audit would i + +…(truncated — see release link)
diff --git a/refresh-summary.json b/refresh-summary.json index 287bf90..cc00612 100644 --- a/refresh-summary.json +++ b/refresh-summary.json @@ -4,12 +4,12 @@ { "name": "zizmor", "repo": "zizmorcore/zizmor", - "from": "1.26.1", - "to": "1.27.0", - "tag": "v1.27.0", - "url": "https://github.com/zizmorcore/zizmor/releases/tag/v1.27.0", - "compareUrl": "https://github.com/zizmorcore/zizmor/compare/v1.26.1...v1.27.0", - "notes": "## New Features 🌈[🔗](https://docs.zizmor.sh/release-notes/#new-feartures)\r\n\r\n- zizmor now has experimental support for workflows that specify parallel steps. See [Usage - Parallel steps](https://docs.zizmor.sh/usage/#parallel-steps) for more information ([#2153](https://github.com/zizmorcore/zizmor/issues/2153))\r\nEnhancements 🌱[🔗](https://docs.zizmor.sh/release-notes/#enhancements)\r\n\r\n- zizmor's handling of paths is now more consistent, particularly when run on Windows ([#2163](https://github.com/zizmorcore/zizmor/issues/2163))\r\n\r\n- zizmor now emits a helpful warning when being run in implicit offline mode ([#2180](https://github.com/zizmorcore/zizmor/issues/2180))\r\n\r\n## Bug Fixes 🐛[🔗](https://docs.zizmor.sh/release-notes/#bug-fixes)\r\n\r\n- Fixed a bug where the [secrets-outside-env](https://docs.zizmor.sh/audits/#secrets-outside-env) audit would not honor ignore comments within the same job scope ([#2157](https://github.com/zizmorcore/zizmor/issues/2157))\r\n\r\n- Fixed a bug where the [ref-version-mismatch](https://docs.zizmor.sh/audits/#ref-version-mismatch) audit would not honor ignore comments within the same steps scope ([#2177](https://github.com/zizmorcore/zizmor/issues/2177))\r\n\r\n- Fixed a bug where `--collect=[MODE]` was not correctly handled when auditing remote inputs ([#2185](https://github.com/zizmorcore/zizmor/issues/2185))" + "from": "1.27.0", + "to": "1.28.0", + "tag": "v1.28.0", + "url": "https://github.com/zizmorcore/zizmor/releases/tag/v1.28.0", + "compareUrl": "https://github.com/zizmorcore/zizmor/compare/v1.27.0...v1.28.0", + "notes": "## Security 🔒[🔗](https://docs.zizmor.sh/release-notes/#security)\r\n\r\n- v1.27.0 contained a logging defect that would print any configured GitHub credentials as part of zizmor's cleartext logging. No versions other than v1.27.0 were affected. See [GHSA-f42p-wjw5-97qh](https://github.com/zizmorcore/zizmor/security/advisories/GHSA-f42p-wjw5-97qh) for full information.\r\n\r\n Many thanks to [@shaanmajid](https://github.com/shaanmajid) for finding and reporting this vulnerability.\r\n\r\n## Enhancements 🌱[🔗](https://docs.zizmor.sh/release-notes/#enhancements)\r\n\r\n- The JSON (v1) output format now includes metadata for each finding's fixes, if the finding has fixes ([#2186](https://github.com/zizmorcore/zizmor/issues/2186))\r\n\r\n- The [dependabot-cooldown](https://docs.zizmor.sh/audits/#dependabot-cooldown) audit is now aware of GitHub's new three-day default cooldown ([#2193](https://github.com/zizmorcore/zizmor/issues/2193))\r\n\r\n- sbt is now recognized as a package-ecosystem in dependabot.yml ([#2211](https://github.com/zizmorcore/zizmor/issues/2211))\r\n\r\n## Bug Fixes 🐛[🔗](https://docs.zizmor.sh/release-notes/#bug-fixes)\r\n\r\n- Fixed a bug where the [template-injection](https://docs.zizmor.sh/audits/#template-injection) audit would incorrectly flag `steps.*.outcome` and `steps.*.conclusion` as injection risks in the default persona ([#2199](https://github.com/zizmorcore/zizmor/issues/2199))\r\n\r\n- Fixed a bug where the [github-env](https://docs.zizmor.sh/audits/#github-env) audit would i\n\n…(truncated — see release link)" } ] } From 65d6953c005d6ae50a51467fe84ef1fddfea8a87 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Mon, 27 Jul 2026 21:58:25 +0900 Subject: [PATCH 2/6] fix(zizmor): follow dependabot-cooldown changes in zizmor 1.28.0 zizmor 1.28.0 taught the dependabot-cooldown audit about GitHub's new implicit three-day default cooldown (zizmorcore/zizmor#2193), changing it in two ways karinto had not absorbed: - The pedantic-only "multi-ecosystem-group cooldowns do not batch updates correctly" finding was dropped. karinto still emitted it, which was the sole hard divergence failing upstream-parity on the 1.28.0 refresh. - "missing cooldown configuration" and "no default-days configured" collapsed into "insufficient implicit default-days (less than 7)": the entry does get a cooldown, GitHub's implicit one, it is just shorter than the threshold. An explicit default-days below 7 keeps its existing wording, and the rule's severity stays Info -- karinto has never mirrored this audit's upstream severity (upstream already emitted warning before 1.28.0). Also adds a dark-launch ignore rule for the message change and drops the now unused cooldown_is_effective helper. --- .changeset/dependabot-cooldown-zizmor-1-28.md | 12 ++++ rules.mbt | 67 +++++-------------- rules_catalog.md | 2 +- ...7-dependabot-cooldown-implicit-default.mjs | 40 +++++++++++ zizmor_rules_test.mbt | 24 ++++++- 5 files changed, 92 insertions(+), 53 deletions(-) create mode 100644 .changeset/dependabot-cooldown-zizmor-1-28.md create mode 100644 scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs diff --git a/.changeset/dependabot-cooldown-zizmor-1-28.md b/.changeset/dependabot-cooldown-zizmor-1-28.md new file mode 100644 index 0000000..9d265a5 --- /dev/null +++ b/.changeset/dependabot-cooldown-zizmor-1-28.md @@ -0,0 +1,12 @@ +--- +"karinto": patch +--- + +`dependabot-cooldown` now tracks zizmor 1.28.0, which taught the audit about GitHub's new implicit three-day default cooldown ([zizmorcore/zizmor#2193](https://github.com/zizmorcore/zizmor/issues/2193)). + +Two behavioural changes: + +- The pedantic-only "`multi-ecosystem-group` cooldowns do not batch updates correctly" finding is **gone**. An `updates:` entry that joins a `multi-ecosystem-group` while configuring a sufficient cooldown is now silent, matching upstream. +- An entry with no `cooldown:` block, or one without `default-days`, now reports `insufficient implicit default-days (less than 7)` instead of `missing cooldown configuration` / `no default-days configured` — the entry does get a cooldown, GitHub's implicit one, it's just shorter than the threshold. + +An explicit `default-days` below 7 keeps its existing `insufficient default-days configured (less than 7)` wording, and the rule's severity is unchanged. diff --git a/rules.mbt b/rules.mbt index 14d8478..40c1345 100644 --- a/rules.mbt +++ b/rules.mbt @@ -2713,22 +2713,23 @@ fn check_misfeature_step( ///| /// Mirrors zizmor's `dependabot-cooldown`: each `updates:` entry is -/// inspected independently and may produce up to four findings -/// (missing block / no default-days / insufficient default-days / -/// multi-ecosystem-group with effective cooldown). Threshold matches -/// upstream's default `days = 7`. +/// inspected independently and may produce one finding — either the +/// explicit `default-days` is below the threshold, or the entry falls back +/// to GitHub's implicit default cooldown (three days since zizmor 1.28.0), +/// which is itself below it. Threshold matches upstream's default +/// `days = 7`. +/// +/// Upstream 1.28.0 dropped the pedantic "`multi-ecosystem-group` with an +/// effective cooldown" finding, so karinto no longer emits it either. fn check_dependabot_cooldown(ctx : WalkCtx, out : Array[Diagnostic]) -> Unit { let threshold = 7 + // GitHub applies this cooldown when an update entry configures none of + // its own; below the threshold, so an entry without an explicit + // `default-days` still gets flagged — just with the implicit wording. + let implicit_message = "insufficient implicit default-days (less than \{threshold})" for um in ctx.updates { let cooldown = um.get("cooldown") match cooldown { - None => - add_diag( - out, - "dependabot-cooldown", - Info, - "missing cooldown configuration", - ) Some(@yaml.Yaml::Map(c)) => { let dd = c.get("default-days") let dd_int = match dd { @@ -2737,13 +2738,7 @@ fn check_dependabot_cooldown(ctx : WalkCtx, out : Array[Diagnostic]) -> Unit { _ => None } match dd_int { - None => - add_diag( - out, - "dependabot-cooldown", - Info, - "no default-days configured", - ) + None => add_diag(out, "dependabot-cooldown", Info, implicit_message) Some(n) => if n < threshold.to_int64() { add_diag( @@ -2754,42 +2749,12 @@ fn check_dependabot_cooldown(ctx : WalkCtx, out : Array[Diagnostic]) -> Unit { ) } } - // multi-ecosystem-group with effective cooldown (pedantic). - if um.contains("multi-ecosystem-group") && cooldown_is_effective(c) { - add_diag( - out, - "dependabot-cooldown", - Info, - "multi-ecosystem-group cooldowns do not batch updates correctly", - ) - } } - _ => - add_diag( - out, - "dependabot-cooldown", - Info, - "missing cooldown configuration", - ) - } - } -} - -///| -/// Returns true when the cooldown block has any positive day budget, -/// matching zizmor's `Cooldown::effective`. -fn cooldown_is_effective(c : Map[String, @yaml.Yaml]) -> Bool { - let fields = [ - "default-days", "semver-major-days", "semver-minor-days", "semver-patch-days", - ] - for k in fields { - match c.get(k) { - Some(@yaml.Yaml::Integer(n)) => if n > 0L { return true } - Some(@yaml.Yaml::Real(d, repr=_)) => if d > 0.0 { return true } - _ => () + // No `cooldown:` block at all (or a non-map one): the implicit + // default applies. + _ => add_diag(out, "dependabot-cooldown", Info, implicit_message) } } - false } ///| diff --git a/rules_catalog.md b/rules_catalog.md index 77d5247..ee54090 100644 --- a/rules_catalog.md +++ b/rules_catalog.md @@ -108,7 +108,7 @@ Per-audit anchors below follow the pattern `https://docs.zizmor.sh/audits/#` | `cache-poisoning` | [`zizmor:cache-poisoning`](https://docs.zizmor.sh/audits/#cache-poisoning) | error | Implemented | Fires when a publishing job restores from cache. Publishing is detected via workflow triggers (`on: release`, `push.tags`, or `push.branches` containing `"release"`) or via a known publisher action (`pypa/gh-action-pypi-publish`, `softprops/action-gh-release`, `goreleaser/goreleaser-action`, `docker/build-push-action` when `push:` is not literally `false`, the AWS / Azure / Google Cloud / Cloudflare deploy actions, etc.). Cache-aware actions (`actions/cache`, `actions/setup-*`, `astral-sh/setup-uv`, `Swatinem/rust-cache`, `docker/setup-buildx-action`, `ramsey/composer-install`, …) are classified per upstream's `Always` / `DefaultBehaviour` / `OptIn` / `OptOut` model with `${{ … }}` expressions evaluated under the tag-push assumption so the tag-only conditional case stays accurate. Recognised tag-push predicates: `startsWith(github.ref, 'refs/tags/')` and `github.ref_type == 'tag'` (single or double-quoted). | | `concurrency-limits` | [`zizmor:concurrency-limits`](https://docs.zizmor.sh/audits/#concurrency-limits) | info | Implemented | | | `dangerous-triggers` | [`zizmor:dangerous-triggers`](https://docs.zizmor.sh/audits/#dangerous-triggers) | error | Implemented | | -| `dependabot-cooldown` | [`zizmor:dependabot-cooldown`](https://docs.zizmor.sh/audits/#dependabot-cooldown) | info | Implemented | Applies to `dependabot.yml`. Each `updates:` entry is inspected for: missing `cooldown:` block, missing `default-days`, `default-days` below the upstream threshold of 7, and the pedantic-only "`multi-ecosystem-group` with an effective cooldown" pattern. | +| `dependabot-cooldown` | [`zizmor:dependabot-cooldown`](https://docs.zizmor.sh/audits/#dependabot-cooldown) | info | Implemented | Applies to `dependabot.yml`. Each `updates:` entry yields at most one finding: an explicit `default-days` below the upstream threshold of 7, or — when the entry has no `cooldown:` block or one without `default-days` — GitHub's implicit default cooldown, which is itself below the threshold ("insufficient implicit default-days"). The pedantic-only "`multi-ecosystem-group` with an effective cooldown" pattern was dropped upstream in zizmor 1.28.0 and is no longer emitted. | | `dependabot-execution` | [`zizmor:dependabot-execution`](https://docs.zizmor.sh/audits/#dependabot-execution) | error | Implemented | Fires on `insecure-external-code-execution: allow` in any `dependabot.yml` update entry. | | `excessive-permissions` | [`zizmor:excessive-permissions`](https://docs.zizmor.sh/audits/#excessive-permissions) | warning | Implemented | Mirrors upstream's pedantic-mode coverage. Workflow-level: emits "default permissions used due to no permissions: block" when no `permissions:` is present, "uses read-all permissions" / "uses write-all permissions" for the blanket forms, and one finding per `write` entry in an Explicit map. Job-level: emits the same `read-all` / `write-all` findings and "default permissions used …" when the parent workflow also lacks a `permissions:` block. Job-level Explicit maps are intentionally silent — upstream punts on per-key over-scope analysis. | | `forbidden-uses` | [`zizmor:forbidden-uses`](https://docs.zizmor.sh/audits/#forbidden-uses) | warning | Implemented | Caller-supplied denylist, passed as the `forbidden` request parameter CSV (wired through `cf/index.js` to `lint_string`'s `forbidden_uses_str`). Each entry is a glob matched against both the bare `owner/repo[/subpath]` and the full `…@ref`. Empty list keeps the rule dormant. | diff --git a/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs b/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs new file mode 100644 index 0000000..fc1fe1f --- /dev/null +++ b/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs @@ -0,0 +1,40 @@ +export const id = "dependabot-cooldown-implicit-default"; +// Transient "shipped fix" rule — safe for prune-diff-rules.yml to remove once +// it matches against prod (prod serves the new wording, the captures are just +// stale). +export const prunable = true; +export const reason = + "This PR follows zizmor 1.28.0's dependabot-cooldown changes (upstream #2193, GitHub's new implicit three-day default cooldown). Two things move: (a) the \"missing cooldown configuration\" and \"no default-days configured\" messages collapse into the single \"insufficient implicit default-days (less than 7)\" wording upstream now uses, and (b) the pedantic-only \"multi-ecosystem-group cooldowns do not batch updates correctly\" finding is gone — zizmor 1.28.0 reports nothing for a multi-ecosystem group that configures a sufficient cooldown, and karinto firing it was the hard divergence that failed upstream-parity on this PR. This rule suppresses a diff only when every moving diagnostic, in either direction, is a dependabot-cooldown finding carrying one of those exact messages: on the captured side the two retired wordings plus the retired multi-ecosystem message, on the replayed side the new implicit wording. Any other rule, or a dependabot-cooldown message outside this set (e.g. the unchanged \"insufficient default-days configured (less than 7)\" appearing or disappearing), is a real regression and is never suppressed. Self-expiring: delete once the change has shipped and prod captures roll over (~30 days post-release)."; + +// Wordings this PR retires. A capture predating the change can still carry +// any of them; none of them should ever appear on the replayed side. +const RETIRED_MESSAGES = new Set([ + "missing cooldown configuration", + "no default-days configured", + "multi-ecosystem-group cooldowns do not batch updates correctly", +]); + +// The single wording that replaces the first two. The multi-ecosystem finding +// has no replacement — it simply disappears. +const NEW_MESSAGE = "insufficient implicit default-days (less than 7)"; + +function isRetired(x) { + return x.rule === "dependabot-cooldown" && RETIRED_MESSAGES.has(x.message); +} + +function isNew(x) { + return x.rule === "dependabot-cooldown" && x.message === NEW_MESSAGE; +} + +export function matches(_capture, _replayed, diff) { + let sawArtifact = false; + for (const d of diff) { + if (d.kind !== "diagnostics") return false; + if (!d.onlyInCaptured.every(isRetired)) return false; + if (!d.onlyInReplayed.every(isNew)) return false; + if (d.onlyInCaptured.length > 0 || d.onlyInReplayed.length > 0) { + sawArtifact = true; + } + } + return sawArtifact; +} diff --git a/zizmor_rules_test.mbt b/zizmor_rules_test.mbt index f5a26d2..52f0fb3 100644 --- a/zizmor_rules_test.mbt +++ b/zizmor_rules_test.mbt @@ -497,12 +497,34 @@ test "dependabot-cooldown: dependabot.yml missing cooldown" { { "rule": "dependabot-cooldown", "severity": "info", - "message": "missing cooldown configuration", + "message": "insufficient implicit default-days (less than 7)", }, ], }) } +///| +/// zizmor 1.28.0 dropped the pedantic "`multi-ecosystem-group` cooldowns do +/// not batch updates correctly" finding, so an entry that groups ecosystems +/// but configures a sufficient cooldown must now be silent. +test "dependabot-cooldown: multi-ecosystem-group with sufficient cooldown is silent" { + let yaml = + #|version: 2 + #|multi-ecosystem-groups: + #| all: + #| schedule: + #| interval: weekly + #|updates: + #| - package-ecosystem: github-actions + #| directory: / + #| multi-ecosystem-group: all + #| cooldown: + #| default-days: 7 + let res = @karinto.lint(yaml) + let hits = res.diagnostics.filter(fn(d) { d.rule == "dependabot-cooldown" }) + inspect(hits.length(), content="0") +} + ///| test "dependabot-execution: insecure-external-code-execution allowed" { let yaml = From ab1c56c88a7ea4cf848797be05489d93ada716d8 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Mon, 27 Jul 2026 22:08:09 +0900 Subject: [PATCH 3/6] fix(zizmor): raise dependabot-cooldown to warning, matching zizmor 1.28.0 zizmor 1.28.0 promoted the too-short `default-days` case from `help` to `warning` with High confidence, so every arm of the audit is now a `warning` upstream. karinto emitted `info` for all of them; mirror upstream instead. Updates the catalogue's default_severity too, since `sarif.mbt` derives the SARIF `level` from it, and widens the dark-launch ignore rule to cover the severity move on the otherwise-unchanged "insufficient default-days configured (less than 7)" message. --- .changeset/dependabot-cooldown-zizmor-1-28.md | 5 +-- rules.mbt | 11 ++++--- rules_catalog.mbt | 2 +- rules_catalog.md | 2 +- ...7-dependabot-cooldown-implicit-default.mjs | 32 +++++++++++++------ zizmor_rules_test.mbt | 2 +- 6 files changed, 35 insertions(+), 19 deletions(-) diff --git a/.changeset/dependabot-cooldown-zizmor-1-28.md b/.changeset/dependabot-cooldown-zizmor-1-28.md index 9d265a5..a7222f9 100644 --- a/.changeset/dependabot-cooldown-zizmor-1-28.md +++ b/.changeset/dependabot-cooldown-zizmor-1-28.md @@ -4,9 +4,10 @@ `dependabot-cooldown` now tracks zizmor 1.28.0, which taught the audit about GitHub's new implicit three-day default cooldown ([zizmorcore/zizmor#2193](https://github.com/zizmorcore/zizmor/issues/2193)). -Two behavioural changes: +Three behavioural changes: - The pedantic-only "`multi-ecosystem-group` cooldowns do not batch updates correctly" finding is **gone**. An `updates:` entry that joins a `multi-ecosystem-group` while configuring a sufficient cooldown is now silent, matching upstream. - An entry with no `cooldown:` block, or one without `default-days`, now reports `insufficient implicit default-days (less than 7)` instead of `missing cooldown configuration` / `no default-days configured` — the entry does get a cooldown, GitHub's implicit one, it's just shorter than the threshold. +- The rule's severity moves from `info` to `warning`, mirroring upstream: zizmor 1.28.0 promoted the too-short `default-days` case from `help` to `warning`, making every arm of the audit a `warning`. This also changes the SARIF `level` reported for the rule. -An explicit `default-days` below 7 keeps its existing `insufficient default-days configured (less than 7)` wording, and the rule's severity is unchanged. +An explicit `default-days` below 7 keeps its existing `insufficient default-days configured (less than 7)` wording. diff --git a/rules.mbt b/rules.mbt index 40c1345..b9f6672 100644 --- a/rules.mbt +++ b/rules.mbt @@ -2720,7 +2720,9 @@ fn check_misfeature_step( /// `days = 7`. /// /// Upstream 1.28.0 dropped the pedantic "`multi-ecosystem-group` with an -/// effective cooldown" finding, so karinto no longer emits it either. +/// effective cooldown" finding, so karinto no longer emits it either, and +/// promoted the remaining findings to `Warning` — matching upstream, where +/// every arm of this audit is now a Medium/High-confidence `warning`. fn check_dependabot_cooldown(ctx : WalkCtx, out : Array[Diagnostic]) -> Unit { let threshold = 7 // GitHub applies this cooldown when an update entry configures none of @@ -2738,13 +2740,14 @@ fn check_dependabot_cooldown(ctx : WalkCtx, out : Array[Diagnostic]) -> Unit { _ => None } match dd_int { - None => add_diag(out, "dependabot-cooldown", Info, implicit_message) + None => + add_diag(out, "dependabot-cooldown", Warning, implicit_message) Some(n) => if n < threshold.to_int64() { add_diag( out, "dependabot-cooldown", - Info, + Warning, "insufficient default-days configured (less than \{threshold})", ) } @@ -2752,7 +2755,7 @@ fn check_dependabot_cooldown(ctx : WalkCtx, out : Array[Diagnostic]) -> Unit { } // No `cooldown:` block at all (or a non-map one): the implicit // default applies. - _ => add_diag(out, "dependabot-cooldown", Info, implicit_message) + _ => add_diag(out, "dependabot-cooldown", Warning, implicit_message) } } } diff --git a/rules_catalog.mbt b/rules_catalog.mbt index 379ad0f..8d52038 100644 --- a/rules_catalog.mbt +++ b/rules_catalog.mbt @@ -628,7 +628,7 @@ pub fn all_rule_specs() -> Array[RuleSpec] { Zizmor, ["zizmor:dependabot-cooldown"], SupplyChain, - Info, + Warning, Low, Implemented, false, diff --git a/rules_catalog.md b/rules_catalog.md index ee54090..03aea86 100644 --- a/rules_catalog.md +++ b/rules_catalog.md @@ -108,7 +108,7 @@ Per-audit anchors below follow the pattern `https://docs.zizmor.sh/audits/#` | `cache-poisoning` | [`zizmor:cache-poisoning`](https://docs.zizmor.sh/audits/#cache-poisoning) | error | Implemented | Fires when a publishing job restores from cache. Publishing is detected via workflow triggers (`on: release`, `push.tags`, or `push.branches` containing `"release"`) or via a known publisher action (`pypa/gh-action-pypi-publish`, `softprops/action-gh-release`, `goreleaser/goreleaser-action`, `docker/build-push-action` when `push:` is not literally `false`, the AWS / Azure / Google Cloud / Cloudflare deploy actions, etc.). Cache-aware actions (`actions/cache`, `actions/setup-*`, `astral-sh/setup-uv`, `Swatinem/rust-cache`, `docker/setup-buildx-action`, `ramsey/composer-install`, …) are classified per upstream's `Always` / `DefaultBehaviour` / `OptIn` / `OptOut` model with `${{ … }}` expressions evaluated under the tag-push assumption so the tag-only conditional case stays accurate. Recognised tag-push predicates: `startsWith(github.ref, 'refs/tags/')` and `github.ref_type == 'tag'` (single or double-quoted). | | `concurrency-limits` | [`zizmor:concurrency-limits`](https://docs.zizmor.sh/audits/#concurrency-limits) | info | Implemented | | | `dangerous-triggers` | [`zizmor:dangerous-triggers`](https://docs.zizmor.sh/audits/#dangerous-triggers) | error | Implemented | | -| `dependabot-cooldown` | [`zizmor:dependabot-cooldown`](https://docs.zizmor.sh/audits/#dependabot-cooldown) | info | Implemented | Applies to `dependabot.yml`. Each `updates:` entry yields at most one finding: an explicit `default-days` below the upstream threshold of 7, or — when the entry has no `cooldown:` block or one without `default-days` — GitHub's implicit default cooldown, which is itself below the threshold ("insufficient implicit default-days"). The pedantic-only "`multi-ecosystem-group` with an effective cooldown" pattern was dropped upstream in zizmor 1.28.0 and is no longer emitted. | +| `dependabot-cooldown` | [`zizmor:dependabot-cooldown`](https://docs.zizmor.sh/audits/#dependabot-cooldown) | warning | Implemented | Applies to `dependabot.yml`. Each `updates:` entry yields at most one finding: an explicit `default-days` below the upstream threshold of 7, or — when the entry has no `cooldown:` block or one without `default-days` — GitHub's implicit default cooldown, which is itself below the threshold ("insufficient implicit default-days"). The pedantic-only "`multi-ecosystem-group` with an effective cooldown" pattern was dropped upstream in zizmor 1.28.0 and is no longer emitted. Severity mirrors upstream, where zizmor 1.28.0 made every arm of the audit a `warning`. | | `dependabot-execution` | [`zizmor:dependabot-execution`](https://docs.zizmor.sh/audits/#dependabot-execution) | error | Implemented | Fires on `insecure-external-code-execution: allow` in any `dependabot.yml` update entry. | | `excessive-permissions` | [`zizmor:excessive-permissions`](https://docs.zizmor.sh/audits/#excessive-permissions) | warning | Implemented | Mirrors upstream's pedantic-mode coverage. Workflow-level: emits "default permissions used due to no permissions: block" when no `permissions:` is present, "uses read-all permissions" / "uses write-all permissions" for the blanket forms, and one finding per `write` entry in an Explicit map. Job-level: emits the same `read-all` / `write-all` findings and "default permissions used …" when the parent workflow also lacks a `permissions:` block. Job-level Explicit maps are intentionally silent — upstream punts on per-key over-scope analysis. | | `forbidden-uses` | [`zizmor:forbidden-uses`](https://docs.zizmor.sh/audits/#forbidden-uses) | warning | Implemented | Caller-supplied denylist, passed as the `forbidden` request parameter CSV (wired through `cf/index.js` to `lint_string`'s `forbidden_uses_str`). Each entry is a glob matched against both the bare `owner/repo[/subpath]` and the full `…@ref`. Empty list keeps the rule dormant. | diff --git a/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs b/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs index fc1fe1f..8179a95 100644 --- a/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs +++ b/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs @@ -1,13 +1,13 @@ export const id = "dependabot-cooldown-implicit-default"; // Transient "shipped fix" rule — safe for prune-diff-rules.yml to remove once -// it matches against prod (prod serves the new wording, the captures are just -// stale). +// it matches against prod (prod serves the new behaviour, the captures are +// just stale). export const prunable = true; export const reason = - "This PR follows zizmor 1.28.0's dependabot-cooldown changes (upstream #2193, GitHub's new implicit three-day default cooldown). Two things move: (a) the \"missing cooldown configuration\" and \"no default-days configured\" messages collapse into the single \"insufficient implicit default-days (less than 7)\" wording upstream now uses, and (b) the pedantic-only \"multi-ecosystem-group cooldowns do not batch updates correctly\" finding is gone — zizmor 1.28.0 reports nothing for a multi-ecosystem group that configures a sufficient cooldown, and karinto firing it was the hard divergence that failed upstream-parity on this PR. This rule suppresses a diff only when every moving diagnostic, in either direction, is a dependabot-cooldown finding carrying one of those exact messages: on the captured side the two retired wordings plus the retired multi-ecosystem message, on the replayed side the new implicit wording. Any other rule, or a dependabot-cooldown message outside this set (e.g. the unchanged \"insufficient default-days configured (less than 7)\" appearing or disappearing), is a real regression and is never suppressed. Self-expiring: delete once the change has shipped and prod captures roll over (~30 days post-release)."; + "This PR follows zizmor 1.28.0's dependabot-cooldown changes (upstream #2193, GitHub's new implicit three-day default cooldown). Three things move: (a) the \"missing cooldown configuration\" and \"no default-days configured\" messages collapse into the single \"insufficient implicit default-days (less than 7)\" wording upstream now uses, (b) the pedantic-only \"multi-ecosystem-group cooldowns do not batch updates correctly\" finding is gone — zizmor 1.28.0 reports nothing for a multi-ecosystem group that configures a sufficient cooldown, and karinto firing it was the hard divergence that failed upstream-parity on this PR, and (c) every remaining arm of the audit is promoted from info to warning, mirroring upstream, so the otherwise-unchanged \"insufficient default-days configured (less than 7)\" finding also moves. This rule suppresses a diff only when every moving diagnostic is a dependabot-cooldown finding on the expected side of that transition: on the captured side an info-severity finding carrying one of the three retired messages or the unchanged one, on the replayed side a warning-severity finding carrying the new implicit message or the unchanged one. Any other rule, an info-severity finding appearing, a warning-severity finding disappearing, or a dependabot-cooldown message outside this set is a real regression and is never suppressed. Self-expiring: delete once the change has shipped and prod captures roll over (~30 days post-release)."; // Wordings this PR retires. A capture predating the change can still carry -// any of them; none of them should ever appear on the replayed side. +// any of them; none should ever appear on the replayed side. const RETIRED_MESSAGES = new Set([ "missing cooldown configuration", "no default-days configured", @@ -18,20 +18,32 @@ const RETIRED_MESSAGES = new Set([ // has no replacement — it simply disappears. const NEW_MESSAGE = "insufficient implicit default-days (less than 7)"; -function isRetired(x) { - return x.rule === "dependabot-cooldown" && RETIRED_MESSAGES.has(x.message); +// Text-wise untouched, but it moves too because of the info → warning bump, +// so it shows up on both sides of the diff. +const UNCHANGED_MESSAGE = "insufficient default-days configured (less than 7)"; + +function isCapturedSide(x) { + return ( + x.rule === "dependabot-cooldown" && + x.severity === "info" && + (RETIRED_MESSAGES.has(x.message) || x.message === UNCHANGED_MESSAGE) + ); } -function isNew(x) { - return x.rule === "dependabot-cooldown" && x.message === NEW_MESSAGE; +function isReplayedSide(x) { + return ( + x.rule === "dependabot-cooldown" && + x.severity === "warning" && + (x.message === NEW_MESSAGE || x.message === UNCHANGED_MESSAGE) + ); } export function matches(_capture, _replayed, diff) { let sawArtifact = false; for (const d of diff) { if (d.kind !== "diagnostics") return false; - if (!d.onlyInCaptured.every(isRetired)) return false; - if (!d.onlyInReplayed.every(isNew)) return false; + if (!d.onlyInCaptured.every(isCapturedSide)) return false; + if (!d.onlyInReplayed.every(isReplayedSide)) return false; if (d.onlyInCaptured.length > 0 || d.onlyInReplayed.length > 0) { sawArtifact = true; } diff --git a/zizmor_rules_test.mbt b/zizmor_rules_test.mbt index 52f0fb3..920d8ff 100644 --- a/zizmor_rules_test.mbt +++ b/zizmor_rules_test.mbt @@ -496,7 +496,7 @@ test "dependabot-cooldown: dependabot.yml missing cooldown" { "diagnostics": [ { "rule": "dependabot-cooldown", - "severity": "info", + "severity": "warning", "message": "insufficient implicit default-days (less than 7)", }, ], From 8f93943143bd9fa36f516a11512bd3a093223af8 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Mon, 27 Jul 2026 22:34:09 +0900 Subject: [PATCH 4/6] chore(catalog): align dependabot-cooldown priority with its new severity The catalogue's `priority` tracks karinto's own severity, not upstream confidence: across the 36 zizmor-sourced rules Kendall tau-b is +0.96 against severity and -0.58 against zizmor's audit confidence, with exact confidence/priority agreement (13/36) falling below the 15.7 expected under independence. zizmor's confidence also varies within a single audit for 10 of the 39 audits that report one, so it could not be a per-rule mirror anyway. Raising the rule to `Warning` therefore left `Low` stranded: it is the Info-band value, while the zizmor `Warning` band is modal at `Medium`. The only other `Warning` + `Low` entries are `forbidden-uses` (dormant until a caller supplies a denylist) and `ref-version-mismatch` (NotPlanned), so this was the sole always-on implemented outlier. No behaviour change -- `priority` has no read sites; it is catalogue metadata only. --- rules_catalog.mbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules_catalog.mbt b/rules_catalog.mbt index 8d52038..595538d 100644 --- a/rules_catalog.mbt +++ b/rules_catalog.mbt @@ -629,7 +629,7 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:dependabot-cooldown"], SupplyChain, Warning, - Low, + Medium, Implemented, false, false, From b6d408468776180ae0ee2944c22f2feb863d7b08 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Mon, 27 Jul 2026 22:59:29 +0900 Subject: [PATCH 5/6] chore(catalog): remove unused Priority enum and field --- DEVELOPMENT.md | 2 +- pkg.generated.mbti | 7 ---- rules_catalog.mbt | 93 ---------------------------------------------- 3 files changed, 1 insertion(+), 101 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 9d4311f..acdd32b 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -84,7 +84,7 @@ captured by `wrangler tail`: `rules_catalog.mbt` is the source of truth for what karinto checks. Each entry carries an ID, human-readable title, source family (actionlint / -zizmor / ghalint), upstream origins, category, severity, priority, +zizmor / ghalint), upstream origins, category, severity, implementation status, and which file kinds it applies to. Of 83 catalogued rules, 68 are implemented; 8 are `Planned` — scaffolded as diff --git a/pkg.generated.mbti b/pkg.generated.mbti index 9b0bcca..0836118 100644 --- a/pkg.generated.mbti +++ b/pkg.generated.mbti @@ -86,12 +86,6 @@ pub(all) struct Position { col : Int } derive(Eq, ToJson, @debug.Debug) -pub(all) enum Priority { - High - Medium - Low -} derive(Eq, @debug.Debug) - pub(all) struct Rule { id : String title : String @@ -108,7 +102,6 @@ pub(all) struct RuleSpec { origins : Array[String] category : Category default_severity : Severity - priority : Priority status : Status applies_to_workflow : Bool applies_to_action : Bool diff --git a/rules_catalog.mbt b/rules_catalog.mbt index 595538d..617c94c 100644 --- a/rules_catalog.mbt +++ b/rules_catalog.mbt @@ -35,13 +35,6 @@ pub(all) enum Category { External } derive(Eq, Debug) -///| -pub(all) enum Priority { - High - Medium - Low -} derive(Eq, Debug) - ///| pub(all) enum Status { Implemented @@ -57,7 +50,6 @@ pub(all) struct RuleSpec { origins : Array[String] category : Category default_severity : Severity - priority : Priority status : Status applies_to_workflow : Bool applies_to_action : Bool @@ -82,7 +74,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:unexpected-keys"], Syntax, Error, - Medium, Implemented, true, true, @@ -94,7 +85,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:missing-required-keys"], Syntax, Error, - High, Implemented, true, true, @@ -106,7 +96,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:empty-mappings"], Syntax, Warning, - Medium, Implemented, true, true, @@ -118,7 +107,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:invalid-mapping-values"], Syntax, Error, - Medium, Implemented, true, true, @@ -130,7 +118,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:expression-syntax"], Expression, Error, - High, Implemented, true, true, @@ -142,7 +129,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:expression-type-mismatch"], Expression, Error, - High, Implemented, true, true, @@ -154,7 +140,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:unknown-context-or-function"], Expression, Error, - High, Implemented, true, true, @@ -166,7 +151,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:context-availability"], Expression, Error, - High, Implemented, true, true, @@ -178,7 +162,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:steps-id-type"], Expression, Error, - Medium, Implemented, true, true, @@ -190,7 +173,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:matrix-type"], Expression, Error, - Medium, Implemented, true, false, @@ -202,7 +184,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:needs-type"], Expression, Error, - Medium, Implemented, true, false, @@ -214,7 +195,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:meaningless-comparison"], Expression, Warning, - Low, Implemented, true, true, @@ -228,7 +208,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:shellcheck-on-run"], External, Warning, - Medium, NotPlanned, true, true, @@ -240,7 +219,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:pyflakes-on-python-run"], External, Warning, - Low, NotPlanned, true, true, @@ -260,7 +238,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:script-injection"], Security, Error, - High, NotPlanned, true, true, @@ -272,7 +249,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:job-needs-graph"], Syntax, Error, - High, Implemented, true, false, @@ -284,7 +260,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:matrix-values"], Syntax, Error, - Medium, Planned, true, false, @@ -296,7 +271,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:webhook-events"], Syntax, Error, - High, Implemented, true, false, @@ -308,7 +282,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:workflow-dispatch-inputs"], Syntax, Error, - Medium, Implemented, true, false, @@ -320,7 +293,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:glob-patterns"], Syntax, Error, - Low, Implemented, true, false, @@ -332,7 +304,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:cron-and-timezone"], Syntax, Error, - Medium, Implemented, true, false, @@ -344,7 +315,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:runner-label"], Syntax, Warning, - Medium, Implemented, true, false, @@ -356,7 +326,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:uses-syntax"], Syntax, Error, - High, Implemented, true, true, @@ -368,7 +337,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:local-action-inputs"], Syntax, Error, - Medium, Planned, true, true, @@ -380,7 +348,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:popular-action-inputs"], Syntax, Error, - Medium, Planned, true, true, @@ -392,7 +359,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:outdated-popular-action-version"], BestPractice, Warning, - Medium, Implemented, true, true, @@ -404,7 +370,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:shell-name"], Syntax, Error, - Low, Implemented, true, true, @@ -416,7 +381,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:duplicate-job-step-ids"], Syntax, Error, - High, Implemented, true, false, @@ -428,7 +392,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:env-var-name"], Syntax, Warning, - Low, Implemented, true, true, @@ -440,7 +403,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:permissions-scope-and-value"], Syntax, Error, - High, Implemented, true, false, @@ -452,7 +414,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:reusable-workflow"], Syntax, Error, - Medium, Implemented, true, false, @@ -464,7 +425,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:job-step-id-naming"], BestPractice, Warning, - Low, Implemented, true, true, @@ -476,7 +436,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:deprecated-workflow-commands"], BestPractice, Warning, - Medium, Implemented, true, true, @@ -488,7 +447,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:constant-if-condition"], BestPractice, Warning, - Low, Implemented, true, true, @@ -500,7 +458,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:action-yml-metadata"], Syntax, Error, - Medium, Implemented, false, true, @@ -516,7 +473,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:deprecated-popular-action-inputs"], BestPractice, Warning, - Low, Planned, true, true, @@ -528,7 +484,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["actionlint:yaml-anchors"], Syntax, Warning, - Low, Implemented, true, true, @@ -545,7 +500,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:anonymous-definition"], BestPractice, Info, - Low, Implemented, true, true, @@ -557,7 +511,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:archived-uses"], SupplyChain, Warning, - Medium, Implemented, true, true, @@ -569,7 +522,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:artipacked"], Security, Warning, - High, Implemented, true, true, @@ -581,7 +533,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:bot-conditions"], Security, Error, - High, Implemented, true, false, @@ -593,7 +544,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:cache-poisoning"], Security, Error, - High, Implemented, true, true, @@ -605,7 +555,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:concurrency-limits"], Performance, Info, - Low, Implemented, true, false, @@ -617,7 +566,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:dangerous-triggers"], Security, Error, - High, Implemented, true, false, @@ -629,7 +577,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:dependabot-cooldown"], SupplyChain, Warning, - Medium, Implemented, false, false, @@ -641,7 +588,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:dependabot-execution"], Security, Error, - High, Implemented, true, false, @@ -653,7 +599,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:excessive-permissions"], Security, Warning, - High, Implemented, true, true, @@ -665,7 +610,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:forbidden-uses"], Config, Warning, - Low, Implemented, true, true, @@ -677,7 +621,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:github-app"], Security, Warning, - Medium, Implemented, true, false, @@ -689,7 +632,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:github-env"], Security, Error, - High, Implemented, true, true, @@ -704,7 +646,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ], Security, Error, - High, Implemented, true, false, @@ -720,7 +661,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:impostor-commit"], SupplyChain, Error, - High, NotPlanned, true, false, @@ -732,7 +672,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:insecure-commands"], Security, Warning, - Medium, Implemented, true, true, @@ -744,7 +683,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:known-vulnerable-actions"], SupplyChain, Error, - High, Implemented, true, true, @@ -756,7 +694,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:misfeature"], BestPractice, Info, - Low, Implemented, true, true, @@ -768,7 +705,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:obfuscation"], Security, Info, - Medium, Implemented, true, true, @@ -780,7 +716,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:overprovisioned-secrets"], Security, Warning, - High, Implemented, true, false, @@ -795,7 +730,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:ref-confusion"], SupplyChain, Warning, - Medium, NotPlanned, true, true, @@ -811,7 +745,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:ref-version-mismatch"], BestPractice, Warning, - Low, NotPlanned, true, false, @@ -824,7 +757,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:secrets-inherit", "ghalint:ghl-004 (deny_inherit_secrets)"], Security, Error, - High, Implemented, true, false, @@ -836,7 +768,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:secrets-outside-env"], Security, Warning, - High, Implemented, true, false, @@ -848,7 +779,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:self-hosted-runner"], Security, Warning, - Medium, Implemented, true, false, @@ -863,7 +793,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:stale-action-refs"], SupplyChain, Info, - Low, NotPlanned, true, true, @@ -875,7 +804,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:superfluous-actions"], BestPractice, Info, - Low, Implemented, true, false, @@ -887,7 +815,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:template-injection"], Security, Error, - High, Implemented, true, true, @@ -899,7 +826,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:undocumented-permissions"], BestPractice, Info, - Low, Implemented, true, false, @@ -914,7 +840,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ], SupplyChain, Warning, - High, Implemented, true, false, @@ -926,7 +851,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:unpinned-tools"], SupplyChain, Warning, - Medium, Implemented, true, false, @@ -941,7 +865,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ], SupplyChain, Warning, - High, Implemented, true, true, @@ -953,7 +876,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:unredacted-secrets"], Security, Error, - High, Implemented, true, false, @@ -965,7 +887,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:unsound-condition"], BestPractice, Warning, - Medium, Implemented, true, true, @@ -977,7 +898,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:unsound-contains"], Security, Warning, - Medium, Implemented, true, true, @@ -989,7 +909,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["zizmor:use-trusted-publishing"], BestPractice, Info, - Medium, Implemented, true, true, @@ -1006,7 +925,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-001 (job_permissions)"], Security, Warning, - High, Implemented, true, false, @@ -1018,7 +936,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-002 (deny_read_all_permission)"], Security, Error, - High, Implemented, true, false, @@ -1030,7 +947,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-003 (deny_write_all_permission)"], Security, Error, - High, Implemented, true, false, @@ -1042,7 +958,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-005 (workflow_secrets)"], Security, Error, - High, Implemented, true, false, @@ -1054,7 +969,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-006 (job_secrets)"], Security, Error, - High, Implemented, true, false, @@ -1066,7 +980,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-009 (github_app_should_limit_repositories)"], Security, Warning, - Medium, Implemented, true, false, @@ -1078,7 +991,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-010 (github_app_should_limit_permissions)"], Security, Warning, - Medium, Implemented, true, false, @@ -1090,7 +1002,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-011 (action_shell_is_required)"], BestPractice, Warning, - Low, Implemented, false, true, @@ -1102,7 +1013,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-012 (job_timeout_minutes_is_required)"], BestPractice, Warning, - Medium, Implemented, true, false, @@ -1114,7 +1024,6 @@ pub fn all_rule_specs() -> Array[RuleSpec] { ["ghalint:ghl-013 (checkout_persist_credentials_should_be_false)"], Security, Error, - High, Implemented, true, false, @@ -1130,7 +1039,6 @@ fn spec( origins : Array[String], category : Category, default_severity : Severity, - priority : Priority, status : Status, applies_to_workflow : Bool, applies_to_action : Bool, @@ -1142,7 +1050,6 @@ fn spec( origins, category, default_severity, - priority, status, applies_to_workflow, applies_to_action, From a4815c65b34bb6a527154f0f06822568d7d71183 Mon Sep 17 00:00:00 2001 From: Akira HIGUCHI Date: Mon, 27 Jul 2026 23:09:45 +0900 Subject: [PATCH 6/6] fix(diff-rules): enforce transition shape in dependabot-cooldown rule The rule checked only that every moving diagnostic was a plausible dependabot-cooldown message on its expected side, never that the two sides lined up. Two real regressions slipped through: a captured-only diff, where the new implicit warning fails to appear at all (an empty onlyInReplayed passes .every() vacuously), and a mis-mapped wording, where a retired message is answered by the wrong replacement. Count the findings per file and require each retired implicit source to be answered by exactly one implicit warning, and each explicit default-days finding to reappear with the same wording. Only the dropped multi-ecosystem-group finding may go unanswered, since upstream gives it no replacement. Per-file rather than global counting so a shortfall in one file cannot be cancelled by a surplus in another. --- ...7-dependabot-cooldown-implicit-default.mjs | 90 ++++++++++++++----- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs b/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs index 8179a95..4833d20 100644 --- a/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs +++ b/scripts/diff-rules/2026-07-dependabot-cooldown-implicit-default.mjs @@ -4,46 +4,94 @@ export const id = "dependabot-cooldown-implicit-default"; // just stale). export const prunable = true; export const reason = - "This PR follows zizmor 1.28.0's dependabot-cooldown changes (upstream #2193, GitHub's new implicit three-day default cooldown). Three things move: (a) the \"missing cooldown configuration\" and \"no default-days configured\" messages collapse into the single \"insufficient implicit default-days (less than 7)\" wording upstream now uses, (b) the pedantic-only \"multi-ecosystem-group cooldowns do not batch updates correctly\" finding is gone — zizmor 1.28.0 reports nothing for a multi-ecosystem group that configures a sufficient cooldown, and karinto firing it was the hard divergence that failed upstream-parity on this PR, and (c) every remaining arm of the audit is promoted from info to warning, mirroring upstream, so the otherwise-unchanged \"insufficient default-days configured (less than 7)\" finding also moves. This rule suppresses a diff only when every moving diagnostic is a dependabot-cooldown finding on the expected side of that transition: on the captured side an info-severity finding carrying one of the three retired messages or the unchanged one, on the replayed side a warning-severity finding carrying the new implicit message or the unchanged one. Any other rule, an info-severity finding appearing, a warning-severity finding disappearing, or a dependabot-cooldown message outside this set is a real regression and is never suppressed. Self-expiring: delete once the change has shipped and prod captures roll over (~30 days post-release)."; + "This PR follows zizmor 1.28.0's dependabot-cooldown changes (upstream #2193, GitHub's new implicit three-day default cooldown). Three things move: (a) the \"missing cooldown configuration\" and \"no default-days configured\" messages collapse into the single \"insufficient implicit default-days (less than 7)\" wording upstream now uses, (b) the pedantic-only \"multi-ecosystem-group cooldowns do not batch updates correctly\" finding is gone — zizmor 1.28.0 reports nothing for a multi-ecosystem group that configures a sufficient cooldown, and karinto firing it was the hard divergence that failed upstream-parity on this PR, and (c) every remaining arm of the audit is promoted from info to warning, mirroring upstream, so the otherwise-unchanged \"insufficient default-days configured (less than 7)\" finding also moves. Membership alone is too weak a test, so this rule additionally enforces the *shape* of the transition, counted per file: each captured info-severity finding whose entry configures no explicit default-days (either retired wording) must be answered by exactly one replayed warning-severity implicit finding, and each captured info-severity \"insufficient default-days configured\" must reappear as exactly one replayed warning of the same wording. Only the dropped multi-ecosystem-group finding is allowed to go unanswered, because upstream gives it no replacement. That makes the two regressions a membership-only test would hide — the new warning failing to appear at all, and a retired wording being remapped onto the wrong replacement — fail the rule instead of being suppressed. Any other rule, an info-severity finding appearing, a warning-severity finding disappearing, or a dependabot-cooldown message outside this set is likewise never suppressed. Self-expiring: delete once the change has shipped and prod captures roll over (~30 days post-release)."; -// Wordings this PR retires. A capture predating the change can still carry -// any of them; none should ever appear on the replayed side. -const RETIRED_MESSAGES = new Set([ +// Wordings this PR retires in favour of the implicit one. Both described an +// `updates:` entry that configures no `default-days` of its own, so upstream +// now answers both with the same single finding. +const IMPLICIT_SOURCES = new Set([ "missing cooldown configuration", "no default-days configured", - "multi-ecosystem-group cooldowns do not batch updates correctly", ]); -// The single wording that replaces the first two. The multi-ecosystem finding -// has no replacement — it simply disappears. +// The pedantic finding upstream dropped outright. Unlike the two above it has +// no replacement, so it is the one captured-side finding allowed to go +// unanswered on the replayed side. +const DROPPED_MESSAGE = + "multi-ecosystem-group cooldowns do not batch updates correctly"; + +// The single wording that replaces IMPLICIT_SOURCES. const NEW_MESSAGE = "insufficient implicit default-days (less than 7)"; // Text-wise untouched, but it moves too because of the info → warning bump, // so it shows up on both sides of the diff. const UNCHANGED_MESSAGE = "insufficient default-days configured (less than 7)"; -function isCapturedSide(x) { - return ( - x.rule === "dependabot-cooldown" && - x.severity === "info" && - (RETIRED_MESSAGES.has(x.message) || x.message === UNCHANGED_MESSAGE) - ); +// Captures come from prod (the old behaviour), so every dependabot-cooldown +// finding on this side is info-severity. Returns the bucket the finding counts +// towards, or null if it is not part of the expected transition. +function classifyCaptured(x) { + if (x.severity !== "info") return null; + if (IMPLICIT_SOURCES.has(x.message)) return "implicit"; + if (x.message === UNCHANGED_MESSAGE) return "unchanged"; + if (x.message === DROPPED_MESSAGE) return "dropped"; + return null; +} + +// The replayed side is this PR's worker, where every arm of the audit is a +// warning and the multi-ecosystem wording no longer exists. +function classifyReplayed(x) { + if (x.severity !== "warning") return null; + if (x.message === NEW_MESSAGE) return "implicit"; + if (x.message === UNCHANGED_MESSAGE) return "unchanged"; + return null; } -function isReplayedSide(x) { - return ( - x.rule === "dependabot-cooldown" && - x.severity === "warning" && - (x.message === NEW_MESSAGE || x.message === UNCHANGED_MESSAGE) - ); +// Diagnostics are compared as one flat multiset per capture and only carry +// `{file?, rule, severity, message}` — `file` is absent for single-result +// captures, hence the "" bucket. Counting per file rather than globally keeps +// a mismatch in one file from being cancelled out by an opposite mismatch in +// another. Returns null if any finding falls outside the expected transition. +function tally(entries, classify) { + const byFile = new Map(); + for (const x of entries) { + if (x.rule !== "dependabot-cooldown") return null; + const bucket = classify(x); + if (bucket === null) return null; + const key = x.file || ""; + let counts = byFile.get(key); + if (!counts) { + counts = { implicit: 0, unchanged: 0, dropped: 0 }; + byFile.set(key, counts); + } + counts[bucket] += 1; + } + return byFile; } +const EMPTY = { implicit: 0, unchanged: 0, dropped: 0 }; + export function matches(_capture, _replayed, diff) { let sawArtifact = false; for (const d of diff) { if (d.kind !== "diagnostics") return false; - if (!d.onlyInCaptured.every(isCapturedSide)) return false; - if (!d.onlyInReplayed.every(isReplayedSide)) return false; + + const captured = tally(d.onlyInCaptured, classifyCaptured); + const replayed = tally(d.onlyInReplayed, classifyReplayed); + if (captured === null || replayed === null) return false; + + for (const file of new Set([...captured.keys(), ...replayed.keys()])) { + const c = captured.get(file) || EMPTY; + const r = replayed.get(file) || EMPTY; + // Every entry that lost a "no explicit default-days" finding must have + // gained exactly one implicit-wording finding, and every entry whose + // explicit-default-days finding only changed severity must reappear with + // the same wording. `dropped` is deliberately excluded: it is the one + // finding upstream removed without a replacement. + if (c.implicit !== r.implicit) return false; + if (c.unchanged !== r.unchanged) return false; + } + if (d.onlyInCaptured.length > 0 || d.onlyInReplayed.length > 0) { sawArtifact = true; }