Goal
When wrangle adds pnpm / Yarn support in a future v0.2+ iteration (currently noted in build/actions/npm/SPEC.md "v0.1 limitations"), do NOT enable setup-node-style package-manager caching for those flows.
Why
The May 2026 Mini Shai-Hulud compromise of TanStack/router exploited pnpm-store cache poisoning. The mechanism:
actions/setup-node (and pnpm/action-setup) cache pnpm-store — a content-addressed store of extracted modules under <integrity-hash>/<file> paths.
- pnpm install hardlinks files from the store into
node_modules/. pnpm does not re-verify that the file at path <hash>/<file> actually hashes to <hash> at install time. The integrity hash is treated as a label, not an enforced guard.
- An attacker with code execution in any workflow on the repo (e.g., via the
pull_request_target "pwn request" pattern) can write malicious content to those store paths. The poisoned store gets cached by GitHub Actions. Subsequent legitimate workflow runs restore the cache, pnpm hardlinks the malicious bytes into node_modules/, the build runs with malicious tools, output is malicious.
- The build is otherwise legitimate (legitimate source, legitimate commit, legitimate signer) — so any provenance attestation (L2 npm-CLI or L3 slsa-github-generator) faithfully signs the malicious output.
The npm equivalent (cache: 'npm', caches ~/.npm tarballs) is NOT vulnerable to this same pattern, because npm ci re-validates each cached tarball's integrity field against package-lock.json on every install. A cache entry whose tarball doesn't hash to the lockfile's pinned integrity is rejected before extraction. This is the trust property pnpm-store lacks.
What this means concretely
When pnpm / Yarn support lands:
- Do NOT set
cache: 'pnpm' or cache: 'yarn' in actions/setup-node invocations from wrangle's build actions.
- Do NOT cache
~/.local/share/pnpm/store / ~/.yarn/cache via actions/cache directly.
- Document the choice in
build/actions/npm/SPEC.md (or the renamed file when it covers all three package managers) with a "Threat model: caching" section pointing at this issue + the TanStack postmortem.
- Add a bats structural test that asserts no pnpm/yarn cache config exists in
build/actions/*/action.yml.
Where this is allowed today
The npm flow on main has cache: 'npm' in build/actions/npm/action.yml. That's safe to keep — see "Why" above. There's a comment block in action.yml already explaining the threat model; expand it when pnpm support lands to spell out the asymmetry.
Out of scope
- Disabling caching entirely. Caching is fine where it's safe (npm). The constraint is package-manager-specific.
- Adding extraction-time re-verification to pnpm-store. That's pnpm's job, not wrangle's.
Related
Goal
When wrangle adds pnpm / Yarn support in a future v0.2+ iteration (currently noted in
build/actions/npm/SPEC.md"v0.1 limitations"), do NOT enable setup-node-style package-manager caching for those flows.Why
The May 2026 Mini Shai-Hulud compromise of TanStack/router exploited pnpm-store cache poisoning. The mechanism:
actions/setup-node(andpnpm/action-setup) cachepnpm-store— a content-addressed store of extracted modules under<integrity-hash>/<file>paths.node_modules/. pnpm does not re-verify that the file at path<hash>/<file>actually hashes to<hash>at install time. The integrity hash is treated as a label, not an enforced guard.pull_request_target"pwn request" pattern) can write malicious content to those store paths. The poisoned store gets cached by GitHub Actions. Subsequent legitimate workflow runs restore the cache, pnpm hardlinks the malicious bytes intonode_modules/, the build runs with malicious tools, output is malicious.The npm equivalent (
cache: 'npm', caches~/.npmtarballs) is NOT vulnerable to this same pattern, becausenpm cire-validates each cached tarball'sintegrityfield againstpackage-lock.jsonon every install. A cache entry whose tarball doesn't hash to the lockfile's pinned integrity is rejected before extraction. This is the trust property pnpm-store lacks.What this means concretely
When pnpm / Yarn support lands:
cache: 'pnpm'orcache: 'yarn'inactions/setup-nodeinvocations from wrangle's build actions.~/.local/share/pnpm/store/~/.yarn/cacheviaactions/cachedirectly.build/actions/npm/SPEC.md(or the renamed file when it covers all three package managers) with a "Threat model: caching" section pointing at this issue + the TanStack postmortem.build/actions/*/action.yml.Where this is allowed today
The npm flow on main has
cache: 'npm'inbuild/actions/npm/action.yml. That's safe to keep — see "Why" above. There's a comment block inaction.ymlalready explaining the threat model; expand it when pnpm support lands to spell out the asymmetry.Out of scope
Related
build/actions/npm/action.yml— currentcache: 'npm'config + threat-model comment.build/actions/npm/SPEC.md— v0.1 limitations noting pnpm/yarn as follow-on.