From 09abd286e7defa90526c76ee4c05c7b820c46f9f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 02:10:53 +0000 Subject: [PATCH 1/2] chore(deps): pin dependencies --- .github/workflows/audit.yaml | 14 ++++---- .github/workflows/ci.yaml | 54 +++++++++++++++--------------- .github/workflows/release-plz.yaml | 6 ++-- .github/workflows/release.yaml | 6 ++-- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml index e85877d..db2c415 100644 --- a/.github/workflows/audit.yaml +++ b/.github/workflows/audit.yaml @@ -47,16 +47,16 @@ jobs: name: cargo-deny runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: audit-deny - name: Generate Cargo.lock # Cargo.lock is gitignored (.gitignore:7-13). Generate one in # the job so the deny run has deterministic resolved versions. run: cargo generate-lockfile - - uses: EmbarkStudios/cargo-deny-action@v2 + - uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2 with: command: check log-level: info @@ -66,9 +66,9 @@ jobs: name: cargo-audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: audit-audit - name: Generate Cargo.lock diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index afc3871..017fa8e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,8 +27,8 @@ jobs: name: fmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: components: rustfmt - run: cargo fmt --all --check @@ -37,11 +37,11 @@ jobs: name: clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: components: clippy - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: clippy - run: cargo clippy --all-targets --all-features -- -D warnings @@ -57,11 +57,11 @@ jobs: # When bumping MSRV, update both places. toolchain: [stable, "1.93.0"] steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # master with: toolchain: ${{ matrix.toolchain }} - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: test-${{ matrix.os }}-${{ matrix.toolchain }} - run: cargo test --all-features @@ -85,9 +85,9 @@ jobs: - label: "default" flags: "" steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: features-${{ matrix.label }} - run: cargo build ${{ matrix.flags }} @@ -123,11 +123,11 @@ jobs: name: wasm runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: targets: wasm32-unknown-unknown - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: wasm - run: cargo build --target wasm32-unknown-unknown --no-default-features @@ -136,18 +136,18 @@ jobs: name: coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: components: llvm-tools-preview - - uses: taiki-e/install-action@v2 + - uses: taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2 with: tool: cargo-llvm-cov - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: coverage - run: cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 with: files: lcov.info fail_ci_if_error: false @@ -159,9 +159,9 @@ jobs: env: RUSTDOCFLAGS: "-D warnings" steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: docs - run: cargo doc --no-deps --all-features @@ -170,11 +170,11 @@ jobs: name: pre-commit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: components: rustfmt, clippy - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: '3.x' # Pin `pre-commit` so CI stays reproducible. The e2e tests assert @@ -187,11 +187,11 @@ jobs: # venv). Keyed on the config hash so a hook/rev change busts the # cache; avoids re-cloning on every run and reduces flakiness from # transient network errors. - - uses: actions/cache@v4 + - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.cache/pre-commit key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: pre-commit # `cargo test --test pre_commit_e2e` exercises pre-commit diff --git a/.github/workflows/release-plz.yaml b/.github/workflows/release-plz.yaml index c706578..f0da29c 100644 --- a/.github/workflows/release-plz.yaml +++ b/.github/workflows/release-plz.yaml @@ -39,15 +39,15 @@ jobs: name: release-plz runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: # release-plz walks conventional-commit history back to the # last release tag — needs full history, not a shallow clone. fetch-depth: 0 persist-credentials: false - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - name: Run release-plz - uses: MarcoIeni/release-plz-action@v0.5 + uses: MarcoIeni/release-plz-action@2eb1d8bcb770b4c48ccfaad919734b38b51958c9 # v0.5 env: # A ref (tag/PR) created with the default GITHUB_TOKEN does NOT # trigger other workflows (GitHub's recursion guard) — so the diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index eadc958..cc4aa96 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -50,14 +50,14 @@ jobs: - target: x86_64-pc-windows-msvc os: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 persist-credentials: false - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: targets: ${{ matrix.target }} - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: shared-key: release-${{ matrix.target }} - name: Install dist From ea90f8f8e82c74209f0de1848c81cf575bba189c Mon Sep 17 00:00:00 2001 From: Jimbo Freedman Date: Mon, 27 Jul 2026 06:50:46 +0100 Subject: [PATCH 2/2] test: accept digest-pinned actions in workflow version assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `renovate.json` extends `helpers:pinGitHubActionDigests` (present since the repo skeleton, ENG-4676), so Renovate rewrites every `uses:` tag into `owner/action@ # vX`. Three assertions written later in ENG-4686 / ENG-4692 matched on the bare tag with a file-wide `contains`, so the repo's own supply-chain policy failed its own tests as soon as Renovate acted on it (PR #17: all 6 `test` rows + `coverage` red). Replaces those with a `pins_action_at` helper that accepts either form — the floating tag or a digest annotated with it — while still rejecting what the original assertions existed to catch: a branch ref (`@main`), an unannotated digest, and a digest whose comment names a different version. Matching is now per-line and exact, so `v0.5` is no longer satisfied by a `v0.51` tag. Fixes the two currently-failing assertions (cargo-deny-action, release-plz-action) and the latent third (rustsec/audit-check), which passes only because Renovate has not resolved a digest for it yet. The helper is duplicated across the two test files rather than shared: each workflow test file in this repo is self-contained and already keeps its own copy of `repo_root`/`read_workflow`. No workflow, config, or library code changed — assertions only. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Jimbo Freedman --- tests/audit_workflow.rs | 56 +++++++++++++++++++++++++++++++------ tests/release_plz_config.rs | 33 ++++++++++++++++++++-- 2 files changed, 79 insertions(+), 10 deletions(-) diff --git a/tests/audit_workflow.rs b/tests/audit_workflow.rs index 03735e7..85f2221 100644 --- a/tests/audit_workflow.rs +++ b/tests/audit_workflow.rs @@ -67,6 +67,44 @@ fn read_deny_toml() -> String { fs::read_to_string(&p).unwrap_or_else(|e| panic!("failed to read {}: {e}", p.display())) } +/// True when `wf` pins `action` to `version` in either of the two forms +/// this repo accepts: +/// +/// - a floating tag — `owner/action@v2` +/// - a digest pin annotated with that tag — `owner/action@<40-hex> # v2` +/// +/// Both are version contracts; the digest form is the stricter of the two. +/// `renovate.json` extends `helpers:pinGitHubActionDigests`, so Renovate +/// rewrites every tag into the digest form and carries the tag over into a +/// trailing comment. Asserting only the bare tag would make the repo's own +/// supply-chain policy fail its own tests (ENG-4706 / PR #17). +/// +/// Still rejected, because they drop the version contract entirely: a +/// branch ref (`@main`, `@master`), a digest with no version comment, and +/// a digest whose comment names a *different* version. +fn pins_action_at(wf: &str, action: &str, version: &str) -> bool { + let needle = format!("{action}@"); + wf.lines().any(|line| { + let Some((_, rest)) = line.split_once(&needle) else { + return false; + }; + // Split the git ref from any trailing `# comment`. + let (git_ref, comment) = match rest.split_once('#') { + Some((r, c)) => (r.trim(), Some(c.trim())), + None => (rest.trim(), None), + }; + // Floating tag: the ref itself IS the version. Exact match, so + // `v0.5` is not satisfied by a `v0.51` tag. + if git_ref == version { + return true; + } + // Digest pin: the version moves into the comment Renovate writes. + git_ref.len() == 40 + && git_ref.chars().all(|c| c.is_ascii_hexdigit()) + && comment == Some(version) + }) +} + // --------------------------------------------------------------------------- // audit.yaml — triggers, schedule, permissions, concurrency // --------------------------------------------------------------------------- @@ -169,13 +207,14 @@ fn audit_workflow_has_separate_concurrency_group() { #[test] fn audit_workflow_uses_cargo_deny_action_v2() { let wf = read_workflow(); - // Tag pinning matters: @v2 floats over v2.x.x; @main would let - // upstream silently change behaviour. Lock the floating major. + // Version pinning matters: @main would let upstream silently change + // behaviour. Lock v2 — either as the floating major tag or as a + // digest pinned to it (see `pins_action_at`). assert!( - wf.contains("EmbarkStudios/cargo-deny-action@v2"), - "audit.yaml must pin EmbarkStudios/cargo-deny-action@v2 \ - (floating major). A different tag breaks the ticket's \ - action-version contract." + pins_action_at(&wf, "EmbarkStudios/cargo-deny-action", "v2"), + "audit.yaml must pin EmbarkStudios/cargo-deny-action to v2 — as \ + `@v2` or as `@ # v2`. A different tag, a branch ref, or an \ + unannotated digest breaks the ticket's action-version contract." ); } @@ -183,8 +222,9 @@ fn audit_workflow_uses_cargo_deny_action_v2() { fn audit_workflow_uses_audit_check_v2() { let wf = read_workflow(); assert!( - wf.contains("rustsec/audit-check@v2"), - "audit.yaml must pin rustsec/audit-check@v2 (floating major)." + pins_action_at(&wf, "rustsec/audit-check", "v2"), + "audit.yaml must pin rustsec/audit-check to v2 — as `@v2` or as \ + `@ # v2`." ); } diff --git a/tests/release_plz_config.rs b/tests/release_plz_config.rs index bc38651..7bd68c0 100644 --- a/tests/release_plz_config.rs +++ b/tests/release_plz_config.rs @@ -50,6 +50,34 @@ fn read_workflow() -> String { read(".github/workflows/release-plz.yaml") } +/// True when `wf` pins `action` to `version`, accepting both the floating +/// tag (`owner/action@v0.5`) and the digest form Renovate rewrites it into +/// (`owner/action@<40-hex> # v0.5`) — `renovate.json` extends +/// `helpers:pinGitHubActionDigests`. Branch refs, bare digests, and +/// digests annotated with a different version are still rejected. +/// +/// Deliberately duplicated from `tests/audit_workflow.rs` (which carries +/// the long rationale): each workflow test file in this repo is +/// self-contained and keeps its own copy of its small string helpers. +fn pins_action_at(wf: &str, action: &str, version: &str) -> bool { + let needle = format!("{action}@"); + wf.lines().any(|line| { + let Some((_, rest)) = line.split_once(&needle) else { + return false; + }; + let (git_ref, comment) = match rest.split_once('#') { + Some((r, c)) => (r.trim(), Some(c.trim())), + None => (rest.trim(), None), + }; + if git_ref == version { + return true; + } + git_ref.len() == 40 + && git_ref.chars().all(|c| c.is_ascii_hexdigit()) + && comment == Some(version) + }) +} + // --- release-plz.toml --- #[test] @@ -137,8 +165,9 @@ fn release_plz_workflow_triggers_only_on_push_to_main() { fn release_plz_workflow_uses_action_v0_5() { let wf = read_workflow(); assert!( - wf.contains("MarcoIeni/release-plz-action@v0.5"), - "release-plz.yaml must pin MarcoIeni/release-plz-action@v0.5." + pins_action_at(&wf, "MarcoIeni/release-plz-action", "v0.5"), + "release-plz.yaml must pin MarcoIeni/release-plz-action to v0.5 — \ + as `@v0.5` or as `@ # v0.5`." ); }