Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/olive-moons-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'stash': patch
---

Correct the release-workflow section of the bundled `stash-supply-chain-security`
skill. It described the no-Actions-cache rule as a property of one file — "no
`cache:`, `package-manager-cache: false`, `pnpm/action-setup` with
`cache: false`" — which is no longer the whole rule.

The gate now follows any local composite action or reusable workflow the job
reaches, so the constraint is on the whole call tree rather than the workflow
file. And every published `uses:` must appear in the script's `AUDITED_ACTIONS`
allowlist: the check cannot open a published action to prove it does not cache,
and caching actions are not reliably named — a `setup-<tool>` action that caches
by default has no `cache:` input and nothing in its name to match. The list is
therefore what is permitted, not what is forbidden, and adding a step to
`release.yml` or `tests-supply-chain.yml` means auditing the action and adding
it there in the same PR.
2 changes: 1 addition & 1 deletion .github/actions/integration-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ runs:
- name: Checkout Repo
uses: actions/checkout@v6

- uses: pnpm/action-setup@v6.0.8
- uses: pnpm/action-setup@v6.0.9
name: Install pnpm
with:
run_install: false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Over-trigger guard. Every `uses:` here is on the allowlist and none of them
# caches, so the gate must stay silent — including on `changesets/action`,
# which is real (release.yml's publish step), third-party, and has nothing to
# do with caching. A rule that fired here would fire on the live release
# workflow.
name: Audited Actions
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: false
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
package-manager-cache: false
- name: Publish to npm
uses: changesets/action@v1.9.0
with:
publish: pnpm run release
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Three more vendors, none of them first-party, none with a `cache:` input.
# The point is that no rule keyed on a list of known cache actions would have
# had these on it — the list is open-ended and grows without this repo hearing
# about it.
name: Cache Family
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: buildjet/cache@v4
- uses: runs-on/cache@v4
- uses: tespkg/actions-cache@v1
- run: pnpm changeset:publish
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# The fail-closed half of the exemption `input-named-cache` earns. This
# composite forwards its `cache` input straight into a step that caches, which
# is what makes skipping the CALLER's `with.cache` safe rather than a hole: the
# justification for skipping is "the body is audited instead", and here the
# audit of the body is what produces the finding.
#
# `package-manager-cache: false` is set on purpose, and satisfies the
# explicit-`false` rule, so the single finding this fixture pins is the
# forwarded `cache:` — the very rule that is exempted at the caller, firing
# inside the composite where the caching actually happens.
name: Cache Passthrough
description: Forwards its `cache` input into actions/setup-node.
inputs:
cache:
description: Which package manager's cache to restore, or empty for none.
required: false
default: ''
runs:
using: composite
steps:
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: ${{ inputs.cache }}
package-manager-cache: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Cachey Restore
description: Restore-only half of the GitHub Actions cache.
runs:
using: composite
steps:
- name: Restore the compiled binding
uses: actions/cache/restore@v4
with:
path: index.node
key: binding-${{ runner.os }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Cachey Save
description: Save-only half of the GitHub Actions cache.
runs:
using: composite
steps:
- name: Save the compiled binding
uses: actions/cache/save@v4
with:
path: index.node
key: binding-${{ runner.os }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Cachey
description: Restores a build artifact from the GitHub Actions cache.
runs:
using: composite
steps:
- name: Restore the compiled binding
uses: actions/cache@v4
with:
path: index.node
key: binding-${{ runner.os }}
- name: Build the binding
shell: bash
run: echo build
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Clean Composite
description: Sets up the toolchain with every cache explicitly disabled.
runs:
using: composite
steps:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: false
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
package-manager-cache: false
- shell: bash
run: pnpm install --frozen-lockfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This file exists only so git tracks the DIRECTORY it sits in, which is named
`action.yml` on purpose. Git cannot track an empty directory, and the directory
is the whole fixture: see composite-dir-action-yml.yml for what it pins.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Declares an input called `cache` and touches no cache at all. `with:` on a
# step that hands off to a local action is that action's declared inputs — an
# input named `cache` has no more to do with the GitHub Actions cache than one
# named `path` does, and a composite is free to declare either.
#
# The step-level twin of what `called-input-named-cache.yml` pins one level up.
name: Input Named Cache
description: Takes an input called `cache` and restores nothing.
inputs:
cache:
description: Reuse `index.node` from the working tree if it is already built.
required: false
default: 'false'
runs:
using: composite
steps:
- name: Build the binding
shell: bash
run: echo "build (reuse=${{ inputs.cache }})"
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# A local action that is NOT a composite: `runs.using` names a Node runtime and
# there is no step list in this file or anywhere this gate can reach. Whatever
# `index.js` does with an input called `cache` is unreadable from here.
#
# So the `with.cache` heuristic stays on a caller of this action, and that is
# the whole reason the exemption is keyed on `runs.using: composite` rather than
# on "the `uses:` starts with `./`". A local `uses:` is already exempt from
# AUDITED_ACTIONS — exempt precisely because the gate audits its body instead —
# so on an action whose body cannot be audited, the caller's `with:` is the only
# signal left. Dropping it for every local `uses:` would make a two-line
# `action.yml` a supported way past the gate.
name: JS Action
description: A local JavaScript action, with no steps for this gate to audit.
inputs:
cache:
description: Passed straight to index.js, which this gate cannot read.
required: false
default: 'false'
runs:
using: node20
main: index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: Loop A
description: Half of a cyclic composite reference.
runs:
using: composite
steps:
- name: Hand off to B
uses: ./.github/actions/loop-b
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Loop B
description: Half of a cyclic composite reference, and it caches.
runs:
using: composite
steps:
- name: Restore the compiled binding
uses: actions/cache@v4
with:
path: index.node
key: binding-${{ runner.os }}
- name: Hand back to A
uses: ./.github/actions/loop-a
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# A composite manifest this gate cannot parse: the `steps:` list is mis-indented
# and js-yaml refuses the file.
#
# This is the unresolvable branch's twin — no step list comes out of it either
# way — so it is reported as un-auditable, and for the same reason it is NOT
# treated as a composite. `runs.using` is unreadable here, and assuming
# `composite` would suppress the caller's `with.cache` heuristic, which is the
# only signal left standing once the body cannot be read.
#
# The `actions/cache@v4` below is deliberate: it is what the caller would be
# hiding behind an unparseable file, and it must never be reported as found,
# because this gate never read it.
name: Malformed Composite
description: A composite action whose YAML does not parse.
runs:
using: composite
steps:
- name: Restore the compiled binding
uses: actions/cache@v4
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Missing Explicit False
description: Sets up the toolchain but never says what it wants from the cache.
runs:
using: composite
steps:
- uses: pnpm/action-setup@v6
with:
run_install: false
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: Outer
description: A composite whose only job is to call another composite.
runs:
using: composite
steps:
- name: Delegate to the inner composite
uses: ./.github/actions/cachey
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Setup Node With Cache
description: Sets up Node with the package-manager cache switched on.
runs:
using: composite
steps:
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: 'pnpm'
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Third Party Cache
description: Restores caches through actions that are not actions/cache.
runs:
using: composite
steps:
- name: Restore the compiled binding
uses: useblacksmith/cache@v5
with:
path: index.node
key: binding-${{ runner.os }}
- name: Restore the Cargo build
uses: Swatinem/rust-cache@v2
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Yaml Ext
description: Spelled `action.yaml`, which GitHub accepts alongside `action.yml`.
runs:
using: composite
steps:
- name: Restore the compiled binding
uses: actions/cache@v4
with:
path: index.node
key: binding-${{ runner.os }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Same caller shape as `composite-input-named-cache.yml` — `with: {cache: true}`
# on a local composite — with the opposite verdict, because the composite hands
# the input to `actions/setup-node`. Exempting the caller must not lose the
# finding, and must not move it: the report names the step inside the composite,
# which is the step to edit.
name: Composite Cache Passthrough
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build the protect-ffi binding
uses: ./.github/actions/cache-passthrough
with:
cache: true
- run: pnpm publish --no-git-checks
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Composite Cache Restore
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build the protect-ffi binding
uses: ./.github/actions/cachey-restore
- run: pnpm publish --no-git-checks
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Composite Cache Save
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build the protect-ffi binding
uses: ./.github/actions/cachey-save
- run: pnpm publish --no-git-checks
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Composite Cache
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build the protect-ffi binding
uses: ./.github/actions/cachey
- run: pnpm publish --no-git-checks
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Composite Clean
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build the protect-ffi binding
uses: ./.github/actions/clean-composite
- run: pnpm publish --no-git-checks
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Composite Cyclic
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Build the protect-ffi binding
uses: ./.github/actions/loop-a
- run: pnpm publish --no-git-checks
Loading
Loading