feat: add npm upgrade step in publish workflow#217
Conversation
🔍 No files have been changedLatest commit: b43f551 Please check your commit. powered by: naverpay size-action |
| - name: Check and upgrade npm | ||
| run: | | ||
| echo "Current npm version:" | ||
| npm --version |
There was a problem hiding this comment.
🔴 The new "Check and upgrade npm" step uses npm install -g npm@latest, which is unpinned — every workflow run may install a different npm version, making builds non-deterministic. More critically, in a publish workflow with registry credentials, a compromised or broken version briefly tagged @latest on the npm registry would be automatically adopted; pin to a specific version like npm@10.9.2 instead.
Extended reasoning...
What the bug is and how it manifests
The newly added step runs npm install -g npm@latest without pinning to a specific version. The @latest dist-tag on the npm registry is a floating pointer that changes whenever a new npm release is published. This means the version of npm installed in the workflow is determined at runtime by whatever is currently tagged @latest, not by anything recorded in source control.
The specific code path that triggers it
Line 57 of .github/workflows/publish.yaml (the new step named "Check and upgrade npm") runs:
npm install -g npm@latest
This executes on every trigger of the publish job — both on push to main (the release path) and on canary/RC issue_comment triggers.
Why existing code doesn't prevent it
There is no version lock or checksum verification anywhere in the workflow for this global npm install. GitHub Actions runners do cache some tooling, but a fresh npm install -g bypasses any such caching and fetches directly from the registry. Nothing in the repository enforces which npm version is installed.
What the impact would be
Two distinct risks apply:
- Non-determinism: Different workflow runs (e.g., a hotfix publish vs. a regular release one week later) may use different npm versions. If a newer npm changes behavior around package resolution, lockfile handling, or publish semantics, the discrepancy would be silent and hard to debug.
- Supply chain risk: This is a publish workflow that pushes packages to the npm registry using
pnpm release(backed by changeset-actions). Downstream consumers of these packages trust that the publishing toolchain was not tampered with. If a malicious or broken npm release is briefly tagged@lateston the registry — a known attack category — the workflow would install it and use it for the publish operation before anyone can react.
How to fix it
Replace the floating tag with a pinned version:
- name: Check and upgrade npm
run: |
echo "Current npm version:"
npm --version
npm install -g npm@10.9.2
echo "Upgraded npm version:"
npm --versionThe pinned version should be deliberately updated in a PR when an upgrade is intentional, making the change explicit and reviewable.
Step-by-step proof
- Workflow run A executes today:
npm@latestresolves to10.9.2. Packages are published successfully. - npm Inc. releases
npm@11.0.0tomorrow and tags it@latest. - Workflow run B executes on the next push to main:
npm install -g npm@latestnow installs11.0.0. npm@11.0.0has a breaking change in its publish behavior (or, in the attack scenario, is a compromised build).pnpm release(or changeset-actions) calls npm under the hood; packages are published with the new/compromised npm binary without any human review of the npm upgrade.- Downstream consumers install the affected package versions.
Related Issue
Describe your changes
Request