Saving in the post step left the whole job between the install and the upload.
Anything running in that window — the job's tests, its build, a dependency's
own install scripts — can rewrite the log on disk, and the job's own cache
write would then publish a record claiming some other lockfile passed
verification, for every later job to restore and trust. No cache credentials
needed: the attacker rides the write the job performs anyway.
The log is complete the moment the install finishes, so it is uploaded there.
The post step still covers a job that installs in a step of its own, where
that is the first point the log is known to be final; the save is idempotent
across the two, and the process-local flags exist because main and post do not
share state within a run.
The module header explained the whole feature where naming the file's purpose
is enough, and the ordering comment described `pnpm store prune` deleting the
log without saying which versions do — pnpm/pnpm#13893 stops deleting it.
pnpm v11 and newer verify every lockfile entry against the configured
supply-chain policies (`minimumReleaseAge`, `trustPolicy`, ...) and memoize
the verdict in `<cacheDir>/lockfile-verified.jsonl`. The action cached only
the store, so every job started with that verdict missing and re-checked the
whole lockfile against the registry — on typescript-eslint's repository,
16.6s of a 17.6s install on Linux and 40.1s of 42.4s on Windows.
The verdict depends on the lockfile content and the policies, never on the
runner, so it is cached under its own key alongside the store cache and
restored without prefix fallback: an entry recorded for a different lockfile
could never be reused. Saving happens before `pnpm store prune`, which drops
the log along with the store's other derived state.
Anything that goes wrong here only costs the next job the re-verification, so
failures are reported as warnings instead of failing the build. Older pnpm
versions never write the log, and the post step then finds nothing to save.
* fix: bin_dest output points to self-updated pnpm, not bootstrap (#247)
`pnpm self-update <version>` writes the target binary to
`${PNPM_HOME}/bin/`, leaving the bootstrap symlink at `${PNPM_HOME}/pnpm`
untouched. The `bin_dest` output was set to `${PNPM_HOME}`, so consumers
invoking `${{ steps.pnpm.outputs.bin_dest }}/pnpm` got the bootstrap
version (currently 11.0.4) instead of the version they requested.
PATH lookup hid the bug: `${PNPM_HOME}/bin` was prepended ahead of
`${PNPM_HOME}`, so `pnpm` resolved from PATH was the right one. Existing
version-respect tests only checked `pnpm --version`, not `bin_dest`.
Resolve `binDest` inside `runSelfInstaller` (target lives in
`${PNPM_HOME}/bin` after self-update, otherwise stays at `${PNPM_HOME}`)
and plumb it through to `setOutputs`. Add a regression test that invokes
`${bin_dest}/pnpm --version` directly across Linux/macOS/Windows.
* test(ci): pass bin_dest via env to survive Windows backslashes
Direct GitHub-expression interpolation of `${{ steps.pnpm.outputs.bin_dest }}`
into the bash script let bash eat the backslashes in the Windows path
(`C:Usersrunneradminsetup-pnpmnode_modules.binbin/pnpm`), failing with
"No such file or directory". Forward the value via env so the path
reaches bash unmangled.
* build: rebuild dist with clean lockfile-matched deps
* add pnpm store caching
* style: format
* no semicolons
* no star imports
* import order
* style: no star imports
---------
Co-authored-by: khai96_ <hvksmr1996@gmail.com>