From f60519fb728d13769a55e25164690c6d99dbc8b3 Mon Sep 17 00:00:00 2001 From: David Meister Date: Mon, 15 Jun 2026 16:36:12 +0000 Subject: [PATCH 1/4] ci: add rainix-npm-blacklist reusable workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream the npm-package blacklist security gate (scan the installed dependency tree for known-malicious packages) into a rainix reusable, so consumers stop copy-pasting the job inline. The reusable owns the boilerplate — the shared nix + cachix preamble, running the caller's `install-command` once, then scanning every listed directory in a single call to the github-chore `npm-blacklist` action via its `working-directories` input. The expensive dependency install runs once and feeds every directory scanned, rather than re-installing per directory. Inputs: `install-command` (required), `working-directories` (whitespace list, default `.`), `cachix-name`. Secret: `CACHIX_AUTH_TOKEN` (optional). Adds a self-test caller that runs the reusable against the subgraph npm project in test/fixture, mirroring how the composites are self-tested. Closes #229 Co-Authored-By: Claude Opus 4.8 --- .../workflows/npm-blacklist-self-test.yaml | 19 ++++++ .github/workflows/rainix-npm-blacklist.yaml | 67 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 .github/workflows/npm-blacklist-self-test.yaml create mode 100644 .github/workflows/rainix-npm-blacklist.yaml diff --git a/.github/workflows/npm-blacklist-self-test.yaml b/.github/workflows/npm-blacklist-self-test.yaml new file mode 100644 index 0000000..6f8e5de --- /dev/null +++ b/.github/workflows/npm-blacklist-self-test.yaml @@ -0,0 +1,19 @@ +## +## Self-test for the `rainix-npm-blacklist.yaml` reusable workflow. +## +## Exercises the reusable end to end against a real npm project in this repo's +## fixture: it installs the fixture's dependency tree and runs the blacklist +## scan over it, exactly as a downstream consumer does. The install command +## populates `node_modules` under `test/fixture/subgraph`, and that same +## directory is the one scanned. +## +name: npm-blacklist-self-test +on: [push] +jobs: + npm-blacklist: + uses: ./.github/workflows/rainix-npm-blacklist.yaml + with: + install-command: nix develop .#wasm-shell -c bash -c "cd test/fixture/subgraph && npm ci" + working-directories: test/fixture/subgraph + secrets: + CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} diff --git a/.github/workflows/rainix-npm-blacklist.yaml b/.github/workflows/rainix-npm-blacklist.yaml new file mode 100644 index 0000000..20863b9 --- /dev/null +++ b/.github/workflows/rainix-npm-blacklist.yaml @@ -0,0 +1,67 @@ +## +## Reusable npm-package blacklist security gate for rainlanguage repos. +## +## Scans the INSTALLED npm dependency tree of one or more workspace packages for +## known-malicious packages, using the shared blacklist data in the +## `rainlanguage/github-chore` `npm-blacklist` composite action. The check reads +## `npm ls --all`, so it must run AFTER dependencies are installed — this +## workflow installs once via the caller's `install-command`, then scans every +## listed directory from that single installed tree. +## +## Consumers replace an inline blacklist job with a short caller that supplies +## the `install-command` and the `working-directories` to scan. +## +name: rainix-npm-blacklist +on: + workflow_call: + inputs: + install-command: + description: >- + Command that installs the npm dependency tree before the scan, e.g. + `nix develop .#wasm-shell -c npm install --no-check`. The blacklist + checker runs `npm ls --all`, so every directory in + `working-directories` must have its `node_modules` populated by this + command. + required: true + type: string + working-directories: + description: >- + Whitespace-separated list of NPM project directories to scan, e.g. a + YAML multiline of ". packages/raindex packages/ui-components". Each + must contain a `package.json` and installed `node_modules` after + `install-command` runs. Defaults to the repository root. + required: false + default: '.' + type: string + cachix-name: + description: Cachix binary cache name to substitute from / push to. + required: false + default: rainlanguage + type: string + secrets: + CACHIX_AUTH_TOKEN: + required: false +jobs: + npm-blacklist: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + # Shared nix + cachix CI preamble (checkout, nix-quick-install, Cachix, + # cache-nix-action). The pinned third-party action SHAs live ONCE in the + # composite; this reusable references it fully qualified at + # `rainlanguage/rainix/...@main`, exactly as downstream consumers do. + - uses: rainlanguage/rainix/.github/actions/nix-cachix-setup@main + with: + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + cachix-name: ${{ inputs.cachix-name }} + # Install the dependency tree once; the blacklist checker inspects it but + # never installs, so a single install feeds every directory scanned below. + - name: Install npm dependencies + run: ${{ inputs.install-command }} + # One call scans every directory from the single installed tree + # (`working-directories` is whitespace-separated). The check fails if any + # listed directory resolves a blacklisted package. + - uses: rainlanguage/github-chore/.github/actions/npm-blacklist@main + with: + working-directories: ${{ inputs.working-directories }} From 56a241e84c86c10e457c5f85a8556a16b8b28ec8 Mon Sep 17 00:00:00 2001 From: David Meister Date: Tue, 16 Jun 2026 01:09:07 +0000 Subject: [PATCH 2/4] style: yamlfmt-normalize composite-action and reusable descriptions yamlfmt reflows the `>-` folded-scalar description bodies in the new rainix-npm-blacklist reusable and in the composite actions onto single lines, matching the `yamlfmt` pre-commit hook that `nix flake check` runs. Co-Authored-By: Claude Opus 4.8 --- .github/actions/cache/action.yml | 9 ++------- .github/actions/checkout/action.yml | 11 ++--------- .github/actions/gh-release/action.yml | 12 +++-------- .github/actions/nix-cachix-setup/action.yml | 22 +++++---------------- .github/actions/rust-cache/action.yml | 11 ++--------- .github/workflows/rainix-npm-blacklist.yaml | 11 ++--------- 6 files changed, 16 insertions(+), 60 deletions(-) diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml index 6c642f0..429ea6c 100644 --- a/.github/actions/cache/action.yml +++ b/.github/actions/cache/action.yml @@ -1,10 +1,6 @@ name: cache description: >- - Single-source wrapper around `actions/cache`. GitHub Actions can't - parameterize a `uses:` ref, so wrapping the step in a composite is the only - way to pin the action's SHA in ONE place across every rainix workflow that - caches build outputs. Each call site passes its own `path` / `key` / - `restore-keys` (the Foundry build cache, the npm cache, etc.). + Single-source wrapper around `actions/cache`. GitHub Actions can't parameterize a `uses:` ref, so wrapping the step in a composite is the only way to pin the action's SHA in ONE place across every rainix workflow that caches build outputs. Each call site passes its own `path` / `key` / `restore-keys` (the Foundry build cache, the npm cache, etc.). inputs: path: description: A list of files, directories, and patterns to cache and restore. @@ -14,8 +10,7 @@ inputs: required: true restore-keys: description: >- - An ordered multiline string listing the prefix-matched keys used to - restore a stale cache if no cache hit occurred for `key`. + An ordered multiline string listing the prefix-matched keys used to restore a stale cache if no cache hit occurred for `key`. required: false default: '' runs: diff --git a/.github/actions/checkout/action.yml b/.github/actions/checkout/action.yml index 6118a1e..f5608a6 100644 --- a/.github/actions/checkout/action.yml +++ b/.github/actions/checkout/action.yml @@ -1,17 +1,10 @@ name: checkout description: >- - Single-source wrapper around `actions/checkout`. GitHub Actions can't - parameterize a `uses:` ref with an env expression, so the only way to pin - the checkout SHA in ONE place across every rainix workflow is to wrap it in a - composite. The `ssh-key` input covers the one call site (autopublish) that - needs a deploy-key checkout; every other site uses the default token checkout. + Single-source wrapper around `actions/checkout`. GitHub Actions can't parameterize a `uses:` ref with an env expression, so the only way to pin the checkout SHA in ONE place across every rainix workflow is to wrap it in a composite. The `ssh-key` input covers the one call site (autopublish) that needs a deploy-key checkout; every other site uses the default token checkout. inputs: ssh-key: description: >- - Optional deploy key (e.g. `secrets.PUBLISH_PRIVATE_KEY`) for a checkout - whose pushes should trigger downstream workflows. A composite action - cannot read `secrets.*`, so the caller must plumb the secret through here. - Empty (the default) falls back to the standard GITHUB_TOKEN checkout. + Optional deploy key (e.g. `secrets.PUBLISH_PRIVATE_KEY`) for a checkout whose pushes should trigger downstream workflows. A composite action cannot read `secrets.*`, so the caller must plumb the secret through here. Empty (the default) falls back to the standard GITHUB_TOKEN checkout. required: false default: '' runs: diff --git a/.github/actions/gh-release/action.yml b/.github/actions/gh-release/action.yml index 1d854a8..f0b8fe1 100644 --- a/.github/actions/gh-release/action.yml +++ b/.github/actions/gh-release/action.yml @@ -1,10 +1,6 @@ name: gh-release description: >- - Single-source wrapper around `softprops/action-gh-release`. GitHub Actions - can't parameterize a `uses:` ref, so wrapping the step in a composite is the - only way to pin the action's SHA in ONE place across the (npm / soldeer) - GitHub Release steps in the autopublish workflow. Each call site passes its - own `tag-name` / `name` / `files`. + Single-source wrapper around `softprops/action-gh-release`. GitHub Actions can't parameterize a `uses:` ref, so wrapping the step in a composite is the only way to pin the action's SHA in ONE place across the (npm / soldeer) GitHub Release steps in the autopublish workflow. Each call site passes its own `tag-name` / `name` / `files`. inputs: tag-name: description: The git tag to create the release against (passed to `tag_name`). @@ -15,14 +11,12 @@ inputs: default: '' files: description: >- - Newline- or comma-separated globs of files to upload as release assets. - Empty (the default) creates a release with no attached assets. + Newline- or comma-separated globs of files to upload as release assets. Empty (the default) creates a release with no attached assets. required: false default: '' github-token: description: >- - GitHub token used to create the release. A composite action cannot read - `secrets.*`, so the caller must plumb `secrets.GITHUB_TOKEN` through here. + GitHub token used to create the release. A composite action cannot read `secrets.*`, so the caller must plumb `secrets.GITHUB_TOKEN` through here. required: true runs: using: composite diff --git a/.github/actions/nix-cachix-setup/action.yml b/.github/actions/nix-cachix-setup/action.yml index 8cfe1ff..860cb26 100644 --- a/.github/actions/nix-cachix-setup/action.yml +++ b/.github/actions/nix-cachix-setup/action.yml @@ -1,16 +1,10 @@ name: nix-cachix-setup description: >- - Shared 'nix + cachix CI' preamble for the rainix reusable workflows: checkout, - nix-quick-install, the Cachix substituter/pusher, and the cache-nix-action Nix - store restore/save. This composite is the single source of truth for the - pinned third-party action SHAs used by that preamble — each SHA lives here once - instead of being copy-pasted across the reusables. + Shared 'nix + cachix CI' preamble for the rainix reusable workflows: checkout, nix-quick-install, the Cachix substituter/pusher, and the cache-nix-action Nix store restore/save. This composite is the single source of truth for the pinned third-party action SHAs used by that preamble — each SHA lives here once instead of being copy-pasted across the reusables. inputs: cachix-auth-token: description: >- - Cachix auth token. A composite action cannot read `secrets.*`, so the - caller reusable must plumb `secrets.CACHIX_AUTH_TOKEN` through to here. - Empty (the default) degrades to a read-only/anonymous Cachix pull. + Cachix auth token. A composite action cannot read `secrets.*`, so the caller reusable must plumb `secrets.CACHIX_AUTH_TOKEN` through to here. Empty (the default) degrades to a read-only/anonymous Cachix pull. required: false default: '' cachix-name: @@ -19,23 +13,17 @@ inputs: default: rainlanguage checkout: description: >- - Run the bundled `actions/checkout` (default). Set to 'false' if the - caller needs a non-default checkout (e.g. an ssh-key deploy-key checkout) - and runs `actions/checkout` itself before calling this composite. + Run the bundled `actions/checkout` (default). Set to 'false' if the caller needs a non-default checkout (e.g. an ssh-key deploy-key checkout) and runs `actions/checkout` itself before calling this composite. required: false default: 'true' cache-nix: description: >- - Run the bundled `cache-nix-action` Nix store restore/save (default). Set - to 'false' if the caller pins a different cache-nix-action version and runs - it itself. + Run the bundled `cache-nix-action` Nix store restore/save (default). Set to 'false' if the caller pins a different cache-nix-action version and runs it itself. required: false default: 'true' gc-max-store-size-macos: description: >- - Optional `gc-max-store-size-macos` for the bundled cache-nix-action. Left - empty (the action's own default) unless a caller needs to cap the macOS - store before saving. + Optional `gc-max-store-size-macos` for the bundled cache-nix-action. Left empty (the action's own default) unless a caller needs to cap the macOS store before saving. required: false default: '' runs: diff --git a/.github/actions/rust-cache/action.yml b/.github/actions/rust-cache/action.yml index 9ba2c58..136bf2e 100644 --- a/.github/actions/rust-cache/action.yml +++ b/.github/actions/rust-cache/action.yml @@ -1,17 +1,10 @@ name: rust-cache description: >- - Single-source wrapper around `Swatinem/rust-cache`. GitHub Actions can't - parameterize a `uses:` ref, so wrapping the step in a composite is the only - way to pin the action's SHA in ONE place across every rainix workflow that - caches Rust build artifacts. The `prefix-key` input covers the one call site - (vercel) that namespaces its cache per-workflow; every other site uses the - action's default key. + Single-source wrapper around `Swatinem/rust-cache`. GitHub Actions can't parameterize a `uses:` ref, so wrapping the step in a composite is the only way to pin the action's SHA in ONE place across every rainix workflow that caches Rust build artifacts. The `prefix-key` input covers the one call site (vercel) that namespaces its cache per-workflow; every other site uses the action's default key. inputs: prefix-key: description: >- - Optional `prefix-key` for `Swatinem/rust-cache` (e.g. one namespaced per - workflow via the github context). Empty (the default) leaves the action's - own default prefix in place. + Optional `prefix-key` for `Swatinem/rust-cache` (e.g. one namespaced per workflow via the github context). Empty (the default) leaves the action's own default prefix in place. required: false default: '' runs: diff --git a/.github/workflows/rainix-npm-blacklist.yaml b/.github/workflows/rainix-npm-blacklist.yaml index 20863b9..482bdb4 100644 --- a/.github/workflows/rainix-npm-blacklist.yaml +++ b/.github/workflows/rainix-npm-blacklist.yaml @@ -17,19 +17,12 @@ on: inputs: install-command: description: >- - Command that installs the npm dependency tree before the scan, e.g. - `nix develop .#wasm-shell -c npm install --no-check`. The blacklist - checker runs `npm ls --all`, so every directory in - `working-directories` must have its `node_modules` populated by this - command. + Command that installs the npm dependency tree before the scan, e.g. `nix develop .#wasm-shell -c npm install --no-check`. The blacklist checker runs `npm ls --all`, so every directory in `working-directories` must have its `node_modules` populated by this command. required: true type: string working-directories: description: >- - Whitespace-separated list of NPM project directories to scan, e.g. a - YAML multiline of ". packages/raindex packages/ui-components". Each - must contain a `package.json` and installed `node_modules` after - `install-command` runs. Defaults to the repository root. + Whitespace-separated list of NPM project directories to scan, e.g. a YAML multiline of ". packages/raindex packages/ui-components". Each must contain a `package.json` and installed `node_modules` after `install-command` runs. Defaults to the repository root. required: false default: '.' type: string From cd1fe3a87f89e6200ce339b7c840cd4bc3d8bef5 Mon Sep 17 00:00:00 2001 From: David Meister Date: Wed, 17 Jun 2026 21:12:07 +0000 Subject: [PATCH 3/4] fix(ci): npm-blacklist working-directory param [3b-attempt] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass working-directory (singular) to the composite action — the action does not accept working-directories (plural), which caused a warning and a fallback to '.' where no package.json exists. Co-Authored-By: Claude --- .github/workflows/rainix-npm-blacklist.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rainix-npm-blacklist.yaml b/.github/workflows/rainix-npm-blacklist.yaml index 482bdb4..d29e6e3 100644 --- a/.github/workflows/rainix-npm-blacklist.yaml +++ b/.github/workflows/rainix-npm-blacklist.yaml @@ -57,4 +57,4 @@ jobs: # listed directory resolves a blacklisted package. - uses: rainlanguage/github-chore/.github/actions/npm-blacklist@main with: - working-directories: ${{ inputs.working-directories }} + working-directory: ${{ inputs.working-directories }} From c184ad12fe127092dab8a90ea15c2111fa4d6432 Mon Sep 17 00:00:00 2001 From: David Meister Date: Fri, 19 Jun 2026 17:23:37 +0000 Subject: [PATCH 4/4] merge(main): resolve conflicts [merge-update]