From c3e782b2de7e54061ef6081c2e22764fc0078888 Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 13 Jun 2026 11:42:55 +0000 Subject: [PATCH 1/3] Pin all third-party GitHub Actions to commit SHAs Every `uses:` reference to a third-party GitHub Action in `.github/workflows/` was pinned only to a mutable tag (`@v4`, `@v2`, `@v30`, ...). A mutable tag lets whoever controls the upstream tag run arbitrary code in our CI the moment it moves -- the supply-chain hole that issue #620 ("pin all deps so we don't get ledgered") calls out, and the exact vector behind the tj-actions/changed-files compromise. Pin all 11 distinct third-party actions to their current full 40-hex commit SHA, keeping a `# vX` comment for readability: actions/checkout v4 / v2 actions/cache v4 actions/setup-node v4 nixbuild/nix-quick-install-action v30 cachix/cachix-action v15 nix-community/cache-nix-action v6 / v7 Swatinem/rust-cache v2 jlumbroso/free-disk-space v1.3.1 softprops/action-gh-release v2 First-party `rainlanguage/*` shared-CI refs (rainix / github-chore reusable workflows and actions) are intentionally left at `@main`: the org tracks those at `main` on purpose, so pinning them is an org-wide decision rather than this repo's. npm / cargo / nix dependency trees are already pinned by their committed lockfiles (package-lock.json, Cargo.lock, flake.lock, soldeer.lock); action refs were the one surface with no lock equivalent. Add a regression guard so a floating ref cannot creep back in: - `script/check-pinned-actions.sh` scans every workflow `uses:` line and prints `OK` iff each third-party ref is a 40-hex SHA, else `UNPINNED: ...` listing the offenders (skips `rainlanguage/*`, local `./` actions and `docker://` refs). Mirrors the existing `script/check-published-deploy-constants.sh` FFI-oracle pattern. - `test/PinnedActions.t.sol` runs that script via `vm.ffi` and asserts the output equals exactly `OK`. Reintroducing e.g. an unpinned `actions/checkout` reds the test with the exact offending ref. CI-only change: no `src/` content changes, so no soldeer publish, no deployed-bytecode change. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/copilot-setup-steps.yml | 12 ++--- .github/workflows/deploy-subgraph.yaml | 10 ++-- .github/workflows/manual-rs-release.yml | 8 +-- .github/workflows/npm-package-release.yml | 18 +++---- .github/workflows/test-js-bindings.yaml | 14 ++--- .github/workflows/test-ui-components.yaml | 10 ++-- .github/workflows/test-webapp.yaml | 10 ++-- .github/workflows/vercel-docs-preview.yaml | 14 ++--- .github/workflows/vercel-docs-prod.yaml | 14 ++--- .github/workflows/vercel-preview.yaml | 14 ++--- .github/workflows/vercel-prod.yaml | 14 ++--- .github/workflows/wasm-artifacts.yaml | 12 ++--- .github/workflows/wasm-browser-test.yaml | 12 ++--- .github/workflows/wasm-test.yaml | 12 ++--- script/check-pinned-actions.sh | 62 ++++++++++++++++++++++ test/PinnedActions.t.sol | 32 +++++++++++ 16 files changed, 181 insertions(+), 87 deletions(-) create mode 100755 script/check-pinned-actions.sh create mode 100644 test/PinnedActions.t.sol diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index f8307c5098..a2e4a3eee3 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -11,29 +11,29 @@ jobs: PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ secrets.WALLETCONNECT_PROJECT_ID || 'test' }} COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v4 - - uses: nixbuild/nix-quick-install-action@v30 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 1G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rust-${{ github.workflow }} - name: Cache npm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/deploy-subgraph.yaml b/.github/workflows/deploy-subgraph.yaml index c65e9b97a8..7fdf31e03c 100644 --- a/.github/workflows/deploy-subgraph.yaml +++ b/.github/workflows/deploy-subgraph.yaml @@ -11,22 +11,22 @@ jobs: GOLDSKY_TOKEN: ${{ secrets.CI_GOLDSKY_TOKEN }} GOLDSKY_SUBGRAPH_NAME: raindex steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 + uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- diff --git a/.github/workflows/manual-rs-release.yml b/.github/workflows/manual-rs-release.yml index 8e40e121ec..ed9ea174e3 100644 --- a/.github/workflows/manual-rs-release.yml +++ b/.github/workflows/manual-rs-release.yml @@ -7,20 +7,20 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@v4 - - uses: nixbuild/nix-quick-install-action@v30 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 + uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} diff --git a/.github/workflows/npm-package-release.yml b/.github/workflows/npm-package-release.yml index 42ea7ef7f8..ddf210ac3a 100644 --- a/.github/workflows/npm-package-release.yml +++ b/.github/workflows/npm-package-release.yml @@ -28,16 +28,16 @@ jobs: version: ${{ env.NEW_VERSION }} steps: # checkout with SSH key to allow pushing version bump commits back to repo - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ssh-key: ${{ secrets.PUBLISH_PRIVATE_KEY }} # WASM builds require significant disk space; free up space to prevent build failures - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: swap-storage: false # install nix for building WASM artifacts and running tests - - uses: nixbuild/nix-quick-install-action@v30 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true @@ -45,30 +45,30 @@ jobs: # pull rainix derivations from shared Cachix; push new ones if the # token is set. continue-on-error so a token miss / Cachix outage # degrades gracefully. - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rust-${{ github.workflow }} - name: Cache npm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} restore-keys: npm-${{ runner.os }}- # setup node with npm registry for OIDC-based publishing (no NPM_TOKEN needed) - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: "24.x" registry-url: "https://registry.npmjs.org" @@ -255,7 +255,7 @@ jobs: - name: Create GitHub Release if: ${{ env.OLD_HASH != env.NEW_HASH }} id: gh_release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: npm-raindex-v${{ env.RAINDEX_NEW_VERSION }}-uc-v${{ env.UC_NEW_VERSION }} name: NPM Package Release raindex v${{ env.RAINDEX_NEW_VERSION }} ui-components v${{ env.UC_NEW_VERSION }} diff --git a/.github/workflows/test-js-bindings.yaml b/.github/workflows/test-js-bindings.yaml index 21e254cefc..59afbde99a 100644 --- a/.github/workflows/test-js-bindings.yaml +++ b/.github/workflows/test-js-bindings.yaml @@ -6,31 +6,31 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rust-${{ github.workflow }} - name: Cache npm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/test-ui-components.yaml b/.github/workflows/test-ui-components.yaml index 10127203ea..0b1fc51cb5 100644 --- a/.github/workflows/test-ui-components.yaml +++ b/.github/workflows/test-ui-components.yaml @@ -16,22 +16,22 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 + uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} diff --git a/.github/workflows/test-webapp.yaml b/.github/workflows/test-webapp.yaml index 5e6e088fdf..9e0762e4ef 100644 --- a/.github/workflows/test-webapp.yaml +++ b/.github/workflows/test-webapp.yaml @@ -16,22 +16,22 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 + uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} diff --git a/.github/workflows/vercel-docs-preview.yaml b/.github/workflows/vercel-docs-preview.yaml index aded1ddbc3..6d49c499d9 100644 --- a/.github/workflows/vercel-docs-preview.yaml +++ b/.github/workflows/vercel-docs-preview.yaml @@ -18,27 +18,27 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: # Restore this branch's own cache if it has one, else fall back to the # cache the prod run saves on main (shared prefix-key). save-if keeps @@ -46,7 +46,7 @@ jobs: prefix-key: rust-vercel-docs save-if: true - name: Cache npm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/vercel-docs-prod.yaml b/.github/workflows/vercel-docs-prod.yaml index 27f7ea6b03..6ecb91a3cc 100644 --- a/.github/workflows/vercel-docs-prod.yaml +++ b/.github/workflows/vercel-docs-prod.yaml @@ -15,33 +15,33 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: # Shared across the docs preview/prod workflows so a preview run (off # main) restores the cache the prod run saves on main. prefix-key: rust-vercel-docs - name: Cache npm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/vercel-preview.yaml b/.github/workflows/vercel-preview.yaml index ca61cb5d6f..365647eac3 100644 --- a/.github/workflows/vercel-preview.yaml +++ b/.github/workflows/vercel-preview.yaml @@ -46,22 +46,22 @@ jobs: COMMIT_SHA: ${{ github.sha }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 + uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} @@ -71,13 +71,13 @@ jobs: # before trying to save a new cache # 1G = 1073741824 gc-max-store-size-linux: 1G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: # Stable shared-key (same in vercel-prod.yaml) so preview + prod share # one warm wasm/cargo cache instead of a separate per-workflow one. shared-key: vercel-webapp - name: Cache npm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/vercel-prod.yaml b/.github/workflows/vercel-prod.yaml index f912dcc48e..efb422fbb0 100644 --- a/.github/workflows/vercel-prod.yaml +++ b/.github/workflows/vercel-prod.yaml @@ -27,22 +27,22 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v6 + uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 with: # restore and save a cache using this key primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} @@ -52,13 +52,13 @@ jobs: # before trying to save a new cache # 1G = 1073741824 gc-max-store-size-linux: 1G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: # Stable shared-key (same in vercel-preview.yaml) so preview + prod # share one warm wasm/cargo cache instead of a separate per-workflow one. shared-key: vercel-webapp - name: Cache npm - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/wasm-artifacts.yaml b/.github/workflows/wasm-artifacts.yaml index b2a934fa01..8faa0724f2 100644 --- a/.github/workflows/wasm-artifacts.yaml +++ b/.github/workflows/wasm-artifacts.yaml @@ -6,27 +6,27 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rust-${{ github.workflow }} - run: nix develop .#wasm-shell -c cargo build --profile release-wasm --target wasm32-unknown-unknown --lib -p raindex_js_api diff --git a/.github/workflows/wasm-browser-test.yaml b/.github/workflows/wasm-browser-test.yaml index e09bc8895c..f8c3baf094 100644 --- a/.github/workflows/wasm-browser-test.yaml +++ b/.github/workflows/wasm-browser-test.yaml @@ -6,30 +6,30 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: # Keep chromium — wasm-pack test --headless --chrome needs it. large-packages: false - - uses: nixbuild/nix-quick-install-action@v30 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rust-${{ github.workflow }} - run: | diff --git a/.github/workflows/wasm-test.yaml b/.github/workflows/wasm-test.yaml index 1fd8526a31..8118dd745b 100644 --- a/.github/workflows/wasm-test.yaml +++ b/.github/workflows/wasm-test.yaml @@ -6,27 +6,27 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Free disk space - uses: jlumbroso/free-disk-space@v1.3.1 - - uses: nixbuild/nix-quick-install-action@v30 + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true keep-outputs = true - - uses: cachix/cachix-action@v15 + - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 continue-on-error: true with: name: rainlanguage authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@v2 + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: prefix-key: rust-${{ github.workflow }} - run: nix develop .#wasm-shell -c bash -c "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner cargo test --target wasm32-unknown-unknown --lib -p raindex_quote -p raindex_bindings -p raindex_js_api -p raindex_common" diff --git a/script/check-pinned-actions.sh b/script/check-pinned-actions.sh new file mode 100755 index 0000000000..8a7428e6ee --- /dev/null +++ b/script/check-pinned-actions.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LicenseRef-DCL-1.0 +# SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +# +# Prints "OK" iff every third-party GitHub Action referenced by a `uses:` line +# in `.github/workflows/` is pinned to a full 40-hex commit SHA rather than a +# mutable ref (a branch like `@main` or a tag like `@v4`). Mutable refs are the +# supply-chain hole this guards: whoever controls the upstream tag controls the +# code our CI runs, so a compromised `@v4` runs in our pipeline the moment it +# moves. +# +# First-party `rainlanguage/*` refs are excluded: the org's shared CI +# (rainix / github-chore reusable workflows + actions) is intentionally tracked +# at `@main`, so pinning those is an org-wide decision rather than this repo's. +# +# Consumed by `test/PinnedActions.t.sol` via FFI. Output is one of: +# OK - every third-party action is SHA-pinned +# UNPINNED: ... - one or more refs use a mutable tag/branch +# +# Always exits 0 so the test sees the message rather than an ffi failure. + +set -euo pipefail + +workflows_dir=".github/workflows" + +# Extract every `uses:` ref. Tolerates leading `- ` and arbitrary indentation, +# quoted or unquoted values, and trailing `# comment`. +refs=$( + grep -rhoE '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*[^[:space:]#]+' "$workflows_dir" \ + | sed -E 's/.*uses:[[:space:]]*//; s/["'\'']//g' +) + +unpinned="" +while IFS= read -r ref; do + [ -z "$ref" ] && continue + + # Skip first-party shared CI (intentionally tracked at @main). + case "$ref" in + rainlanguage/*) continue ;; + esac + + # Local actions (`./...`) and docker refs are not tag-pinnable here. + case "$ref" in + ./*) continue ;; + docker://*) continue ;; + esac + + # Split owner/repo[/path]@ref on the LAST '@'. + pin="${ref##*@}" + + # A pinned ref is exactly 40 lowercase hex chars (a full commit SHA). + if ! printf '%s' "$pin" | grep -qE '^[0-9a-f]{40}$'; then + unpinned="$unpinned $ref" + fi +done <<<"$refs" + +if [ -n "$unpinned" ]; then + printf 'UNPINNED:%s' "$unpinned" + exit 0 +fi + +printf 'OK' diff --git a/test/PinnedActions.t.sol b/test/PinnedActions.t.sol new file mode 100644 index 0000000000..b866742d10 --- /dev/null +++ b/test/PinnedActions.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {Test} from "forge-std-1.16.1/src/Test.sol"; + +/// @title PinnedActionsTest +/// @notice Every third-party GitHub Action referenced by a `uses:` line in +/// `.github/workflows/` must be pinned to a full 40-hex commit SHA, never a +/// mutable tag (a "v4" tag) or branch (a "main" branch). A mutable ref lets +/// whoever controls the upstream tag inject code into our CI the moment it +/// moves (https://github.com/rainlanguage/raindex/issues/620). First-party +/// `rainlanguage/*` shared-CI refs are intentionally excluded -- pinning those +/// is an org-wide decision, not this repo's. +/// +/// `script/check-pinned-actions.sh` enumerates the refs (via FFI) and returns +/// `OK` only when every third-party action is SHA-pinned; otherwise it returns +/// `UNPINNED: ...` listing the offenders. Reintroducing a floating ref +/// (e.g. an unpinned "actions/checkout" at a tag) therefore reds this test +/// with the exact ref. +contract PinnedActionsTest is Test { + function testEveryThirdPartyActionIsShaPinned() external { + string[] memory cmd = new string[](2); + cmd[0] = "bash"; + cmd[1] = "script/check-pinned-actions.sh"; + bytes memory out = vm.ffi(cmd); + + // On failure the actual value is `UNPINNED: ...`, naming each + // mutable third-party action ref so the regression is obvious. + assertEq(string(out), "OK", "a third-party github action is not pinned to a commit sha"); + } +} From 11b4e5f966530801b5f5379336bccee4e1fd4d17 Mon Sep 17 00:00:00 2001 From: David Meister Date: Mon, 15 Jun 2026 17:27:42 +0000 Subject: [PATCH 2/3] ci: inherit shared action pins via rainix composites Rework the action-pinning approach: instead of inline-pinning the shared nix/cachix CI preamble actions (checkout, nix-quick-install, cachix-action, cache-nix-action, rust-cache, actions/cache, action-gh-release) in each raindex workflow, consume the rainix composite actions (rainlanguage/rainix/.github/actions/*) that already pin every one of those SHAs once. The pins are inherited and maintained in rainix. Twelve workflows now delegate their preamble to the composites: copilot-setup-steps, deploy-subgraph, manual-rs-release, npm-package-release, test-js-bindings, test-ui-components, test-webapp, vercel-docs-preview, vercel-docs-prod, wasm-artifacts, wasm-browser-test, wasm-test. Workflows that free disk space between checkout and nix-quick-install (or do an ssh-key checkout) pass checkout: false and run checkout themselves first; nix-cachix-setup's gc-max-store-size-linux carries each workflow's store budget. free-disk-space and setup-node have no rainix composite wrapper, so they stay inline SHA-pinned. The vercel-preview / vercel-prod webapp deploys keep their inline pins too; they are being migrated to the rainix-vercel reusable workflow separately. check-pinned-actions.sh / PinnedActions.t.sol require a SHA pin for every third-party action that remains inline and exempt first-party rainlanguage/* refs, so a workflow satisfies the invariant either by inline-pinning or by delegating to a rainix composite that pins it. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/copilot-setup-steps.yml | 25 ++++-------- .github/workflows/deploy-subgraph.yaml | 24 ++++-------- .github/workflows/manual-rs-release.yml | 26 +++---------- .github/workflows/npm-package-release.yml | 44 ++++++++-------------- .github/workflows/test-js-bindings.yaml | 29 +++++--------- .github/workflows/test-ui-components.yaml | 29 ++++---------- .github/workflows/test-webapp.yaml | 29 ++++---------- .github/workflows/vercel-docs-preview.yaml | 33 ++++++---------- .github/workflows/vercel-docs-prod.yaml | 29 +++++--------- .github/workflows/wasm-artifacts.yaml | 27 +++++-------- .github/workflows/wasm-browser-test.yaml | 27 +++++-------- .github/workflows/wasm-test.yaml | 27 +++++-------- script/check-pinned-actions.sh | 8 +++- test/PinnedActions.t.sol | 4 +- 14 files changed, 118 insertions(+), 243 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index a2e4a3eee3..22715fc9c9 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -11,29 +11,18 @@ jobs: PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ secrets.WALLETCONNECT_PROJECT_ID || 'test' }} COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (checkout, nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} gc-max-store-size-linux: 1G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: prefix-key: rust-${{ github.workflow }} - name: Cache npm - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: rainlanguage/rainix/.github/actions/cache@main with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/deploy-subgraph.yaml b/.github/workflows/deploy-subgraph.yaml index 7fdf31e03c..645697e8c5 100644 --- a/.github/workflows/deploy-subgraph.yaml +++ b/.github/workflows/deploy-subgraph.yaml @@ -11,25 +11,17 @@ jobs: GOLDSKY_TOKEN: ${{ secrets.CI_GOLDSKY_TOKEN }} GOLDSKY_SUBGRAPH_NAME: raindex steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} gc-max-store-size-linux: 1G # subgraph-deploy builds from the committed subgraph/abis + subgraph/generated, # so it needs no forge build and runs on the slim subgraph shell. diff --git a/.github/workflows/manual-rs-release.yml b/.github/workflows/manual-rs-release.yml index ed9ea174e3..3fe3435cd6 100644 --- a/.github/workflows/manual-rs-release.yml +++ b/.github/workflows/manual-rs-release.yml @@ -7,28 +7,12 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (checkout, nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 - with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }}- - # collect garbage until the Nix store size (in bytes) is at most this number - # before trying to save a new cache - # 1G = 1073741824 + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} gc-max-store-size-linux: 1G - run: nix develop .#rust-shell --command cargo release --workspace # forwards status to telegram chat if this ci fails or gets canceled, only runs for default branch diff --git a/.github/workflows/npm-package-release.yml b/.github/workflows/npm-package-release.yml index ddf210ac3a..12248ae260 100644 --- a/.github/workflows/npm-package-release.yml +++ b/.github/workflows/npm-package-release.yml @@ -28,7 +28,7 @@ jobs: version: ${{ env.NEW_VERSION }} steps: # checkout with SSH key to allow pushing version bump commits back to repo - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main with: ssh-key: ${{ secrets.PUBLISH_PRIVATE_KEY }} # WASM builds require significant disk space; free up space to prevent build failures @@ -36,32 +36,19 @@ jobs: uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: swap-storage: false - # install nix for building WASM artifacts and running tests - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because the ssh-key checkout above and + # free-disk-space both run before nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - # pull rainix derivations from shared Cachix; push new ones if the - # token is set. continue-on-error so a token miss / Cachix outage - # degrades gracefully. - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- - gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: prefix-key: rust-${{ github.workflow }} - name: Cache npm - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: rainlanguage/rainix/.github/actions/cache@main with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} @@ -251,19 +238,18 @@ jobs: git push -u origin "npm-raindex-v${{ env.RAINDEX_NEW_VERSION }}-uc-v${{ env.UC_NEW_VERSION }}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Create gitHub release with tarballs + # Create gitHub release with tarballs. The softprops/action-gh-release SHA + # is pinned once in the rainix composite. - name: Create GitHub Release if: ${{ env.OLD_HASH != env.NEW_HASH }} - id: gh_release - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 + uses: rainlanguage/rainix/.github/actions/gh-release@main with: - tag_name: npm-raindex-v${{ env.RAINDEX_NEW_VERSION }}-uc-v${{ env.UC_NEW_VERSION }} + tag-name: npm-raindex-v${{ env.RAINDEX_NEW_VERSION }}-uc-v${{ env.UC_NEW_VERSION }} name: NPM Package Release raindex v${{ env.RAINDEX_NEW_VERSION }} ui-components v${{ env.UC_NEW_VERSION }} files: | raindex_npm_package_${{ env.RAINDEX_NEW_VERSION }}.tgz ui_components_npm_package_${{ env.UC_NEW_VERSION }}.tgz - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} # forwards status to telegram chat if this ci fails or gets canceled, only runs for default branch - name: Forward CI Status if: always() diff --git a/.github/workflows/test-js-bindings.yaml b/.github/workflows/test-js-bindings.yaml index 59afbde99a..da7bc18638 100644 --- a/.github/workflows/test-js-bindings.yaml +++ b/.github/workflows/test-js-bindings.yaml @@ -6,31 +6,22 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- - gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: prefix-key: rust-${{ github.workflow }} - name: Cache npm - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: rainlanguage/rainix/.github/actions/cache@main with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/test-ui-components.yaml b/.github/workflows/test-ui-components.yaml index 0b1fc51cb5..d6b4db6dcc 100644 --- a/.github/workflows/test-ui-components.yaml +++ b/.github/workflows/test-ui-components.yaml @@ -16,30 +16,17 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 - with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }}- - # collect garbage until the Nix store size (in bytes) is at most this number - # before trying to save a new cache - # 1G = 1073741824 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} gc-max-store-size-linux: 10G - run: | nix develop .#wasm-shell -c bash -c ' diff --git a/.github/workflows/test-webapp.yaml b/.github/workflows/test-webapp.yaml index 9e0762e4ef..d2946ea3bf 100644 --- a/.github/workflows/test-webapp.yaml +++ b/.github/workflows/test-webapp.yaml @@ -16,30 +16,17 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@135667ec418502fa5a3598af6fb9eb733888ce6a # v6 - with: - # restore and save a cache using this key - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - # if there's no cache hit, restore a cache by this prefix - restore-prefixes-first-match: nix-${{ runner.os }}- - # collect garbage until the Nix store size (in bytes) is at most this number - # before trying to save a new cache - # 1G = 1073741824 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} gc-max-store-size-linux: 10G - run: | nix develop .#wasm-shell -c bash -c ' diff --git a/.github/workflows/vercel-docs-preview.yaml b/.github/workflows/vercel-docs-preview.yaml index 6d49c499d9..455b6cfd03 100644 --- a/.github/workflows/vercel-docs-preview.yaml +++ b/.github/workflows/vercel-docs-preview.yaml @@ -18,35 +18,24 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- - gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: # Restore this branch's own cache if it has one, else fall back to the - # cache the prod run saves on main (shared prefix-key). save-if keeps - # this branch's cache warm across re-runs. + # cache the prod run saves on main (shared prefix-key). prefix-key: rust-vercel-docs - save-if: true - name: Cache npm - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: rainlanguage/rainix/.github/actions/cache@main with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/vercel-docs-prod.yaml b/.github/workflows/vercel-docs-prod.yaml index 6ecb91a3cc..ab030e8c29 100644 --- a/.github/workflows/vercel-docs-prod.yaml +++ b/.github/workflows/vercel-docs-prod.yaml @@ -15,33 +15,24 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- - gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: # Shared across the docs preview/prod workflows so a preview run (off # main) restores the cache the prod run saves on main. prefix-key: rust-vercel-docs - name: Cache npm - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + uses: rainlanguage/rainix/.github/actions/cache@main with: path: ~/.npm key: npm-${{ runner.os }}-${{ github.workflow }}-${{ hashFiles('**/package-lock.json') }} diff --git a/.github/workflows/wasm-artifacts.yaml b/.github/workflows/wasm-artifacts.yaml index 8faa0724f2..4dfdd5ca5a 100644 --- a/.github/workflows/wasm-artifacts.yaml +++ b/.github/workflows/wasm-artifacts.yaml @@ -6,27 +6,18 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- - gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: prefix-key: rust-${{ github.workflow }} - run: nix develop .#wasm-shell -c cargo build --profile release-wasm --target wasm32-unknown-unknown --lib -p raindex_js_api diff --git a/.github/workflows/wasm-browser-test.yaml b/.github/workflows/wasm-browser-test.yaml index 1d021a80a3..6f39ac0333 100644 --- a/.github/workflows/wasm-browser-test.yaml +++ b/.github/workflows/wasm-browser-test.yaml @@ -6,30 +6,21 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 with: # Keep chromium — wasm-pack test --headless --chrome needs it. large-packages: false - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- - gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: prefix-key: rust-${{ github.workflow }} - run: | diff --git a/.github/workflows/wasm-test.yaml b/.github/workflows/wasm-test.yaml index 8118dd745b..f6934ecb0f 100644 --- a/.github/workflows/wasm-test.yaml +++ b/.github/workflows/wasm-test.yaml @@ -6,27 +6,18 @@ jobs: env: COMMIT_SHA: ${{ github.sha }} steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: rainlanguage/rainix/.github/actions/checkout@main - name: Free disk space uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 + # Shared nix + cachix preamble (nix-quick-install, Cachix, + # cache-nix-action). The third-party action SHAs are pinned once in the + # rainix composite. checkout: false because free-disk-space runs between + # the checkout above and nix-quick-install. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main with: - nix_conf: | - keep-env-derivations = true - keep-outputs = true - - uses: cachix/cachix-action@ad2ddac53f961de1989924296a1f236fcfbaa4fc # v15 - continue-on-error: true - with: - name: rainlanguage - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} - useDaemon: false - - name: Restore and save Nix store - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 - with: - primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}- - gc-max-store-size-linux: 8G - - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + checkout: false + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + - uses: rainlanguage/rainix/.github/actions/rust-cache@main with: prefix-key: rust-${{ github.workflow }} - run: nix develop .#wasm-shell -c bash -c "CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER=wasm-bindgen-test-runner cargo test --target wasm32-unknown-unknown --lib -p raindex_quote -p raindex_bindings -p raindex_js_api -p raindex_common" diff --git a/script/check-pinned-actions.sh b/script/check-pinned-actions.sh index 8a7428e6ee..5da2727e36 100755 --- a/script/check-pinned-actions.sh +++ b/script/check-pinned-actions.sh @@ -9,9 +9,13 @@ # code our CI runs, so a compromised `@v4` runs in our pipeline the moment it # moves. # -# First-party `rainlanguage/*` refs are excluded: the org's shared CI -# (rainix / github-chore reusable workflows + actions) is intentionally tracked +# First-party `rainlanguage/*` refs are excluded: the org's shared CI (rainix / +# github-chore reusable workflows + composite actions) is intentionally tracked # at `@main`, so pinning those is an org-wide decision rather than this repo's. +# The shared third-party actions used by the nix/cachix CI preamble are wrapped +# in rainix composite actions that pin each SHA once, so a workflow satisfies +# this check either by SHA-pinning a third-party action inline or by delegating +# to a `rainlanguage/rainix/.github/actions/*` composite that already pins it. # # Consumed by `test/PinnedActions.t.sol` via FFI. Output is one of: # OK - every third-party action is SHA-pinned diff --git a/test/PinnedActions.t.sol b/test/PinnedActions.t.sol index b866742d10..2f2abe51ec 100644 --- a/test/PinnedActions.t.sol +++ b/test/PinnedActions.t.sol @@ -11,7 +11,9 @@ import {Test} from "forge-std-1.16.1/src/Test.sol"; /// whoever controls the upstream tag inject code into our CI the moment it /// moves (https://github.com/rainlanguage/raindex/issues/620). First-party /// `rainlanguage/*` shared-CI refs are intentionally excluded -- pinning those -/// is an org-wide decision, not this repo's. +/// is an org-wide decision, not this repo's. A workflow therefore satisfies the +/// invariant either by SHA-pinning a third-party action inline or by delegating +/// to a `rainlanguage/rainix/.github/actions/*` composite that pins it once. /// /// `script/check-pinned-actions.sh` enumerates the refs (via FFI) and returns /// `OK` only when every third-party action is SHA-pinned; otherwise it returns From a8fa4e9f8dc0837b540f98ac9741d60dc444613f Mon Sep 17 00:00:00 2001 From: David Meister Date: Fri, 19 Jun 2026 21:37:27 +0000 Subject: [PATCH 3/3] fix(ci): rainix-sol-test [3b-attempt]: pin unpinned GitHub Actions SHA refs in vercel-preview and vercel-prod npm-blacklist jobs --- .github/workflows/vercel-preview.yaml | 6 +++--- .github/workflows/vercel-prod.yaml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vercel-preview.yaml b/.github/workflows/vercel-preview.yaml index b3fb18c9fd..e3bc536402 100644 --- a/.github/workflows/vercel-preview.yaml +++ b/.github/workflows/vercel-preview.yaml @@ -59,8 +59,8 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 - - uses: nixbuild/nix-quick-install-action@v30 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true @@ -72,7 +72,7 @@ jobs: authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}- diff --git a/.github/workflows/vercel-prod.yaml b/.github/workflows/vercel-prod.yaml index e48335f6f4..9e3b608984 100644 --- a/.github/workflows/vercel-prod.yaml +++ b/.github/workflows/vercel-prod.yaml @@ -56,8 +56,8 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 - - uses: nixbuild/nix-quick-install-action@v30 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - uses: nixbuild/nix-quick-install-action@5bb6a3b3abe66fd09bbf250dce8ada94f856a703 # v30 with: nix_conf: | keep-env-derivations = true @@ -69,7 +69,7 @@ jobs: authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} useDaemon: false - name: Restore and save Nix store - uses: nix-community/cache-nix-action@v7 + uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7 with: primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} restore-prefixes-first-match: nix-${{ runner.os }}-