Skip to content

Add .npmrc to harden npm install against supply-chain risk#2479

Merged
westonruter merged 4 commits into
trunkfrom
add/npmrc
May 14, 2026
Merged

Add .npmrc to harden npm install against supply-chain risk#2479
westonruter merged 4 commits into
trunkfrom
add/npmrc

Conversation

@westonruter
Copy link
Copy Markdown
Member

@westonruter westonruter commented May 14, 2026

Summary

Adds an .npmrc to the Performance repo to harden npm install against supply-chain attacks and to align with the rest of the WordPress ecosystem.

Sister PRs in adjacent WordPress projects:

Relevant technical choices

Supply-chain hardening (new to the WordPress ecosystem in this repo)

  • ignore-scripts = true — blocks preinstall / install / postinstall lifecycle scripts in dependencies. This is the main entry point exploited by recent npm supply-chain attacks (malicious package executes arbitrary code at install time). Neither wordpress-develop nor gutenberg set this today; this repo goes one step further.
    • Side effect: it also disables this repo's own prepare script (which sets up husky). Contributors must now run npm run prepare once after npm install to wire up the git hooks. This is documented in the Performance Lab section of the WordPress Performance Team Handbook.
  • min-release-age = 7 — commented out for now. Refuses to install packages released within the last 7 days, mitigating fast-pulled malicious releases (similar to the Gutenberg change in Add a 1-day minimum release age to npm installs gutenberg#78191, but at 7 days vs. Gutenberg's 1 day). Requires npm ≥ 11; left commented until the project's minimum npm version is raised (the WordPress/ai sister PR raised Node to 24 / npm to 11 for this same reason — we can follow when ready).

Alignment with wordpress-develop and gutenberg

Adds the standard set already present in both wordpress-develop/.npmrc and gutenberg/.npmrc:

  • engine-strict = true — enforces the existing engines field in package.json (Node ≥ 20.19.0, npm ≥ 10.2.3). Without this, npm only emits a warning, which contributors routinely miss. Also a prerequisite for cleanly enabling min-release-age later. (Originally introduced in Gutenberg in Add "engines" to the package.json and "engine-strict = true" to the .… gutenberg#23600.) Commented out per 3484824.
  • lockfile-version = 3 — pins the lockfile format so contributors on differing npm versions don't accidentally regenerate it in an older format. Likely a no-op for current contributors on npm 10+, but worth locking in. (Introduced in Gutenberg in Update npm lockfile to version 3 gutenberg#65923.)
  • prefer-dedupe = true — tells npm install to deduplicate rather than nest when adding new deps. Smaller node_modules, marginally more reproducible installs. (Introduced in Gutenberg in Set prefer-dedupe as the default. gutenberg#61630.)
  • legacy-peer-deps = true — restores npm 6-style peer-dep behavior. The @wordpress/* peer-dep graph clashes with npm 7+ strict resolution; both wordpress-develop and gutenberg use this setting. Adding it preemptively for consistency.
  • save-exact = true — pins exact versions when contributors run npm install <pkg>. Existing ^/~ ranges in package.json aren't rewritten — they'll converge over time as deps are added/updated by contributors. (Dependabot preserves whatever prefix is already on each line, so this doesn't retroactively pin existing entries; the package-lock.json is what actually enforces reproducibility at install time.)

Why the existing package-lock.json isn't enough

The lockfile already pins the full transitive tree at install time, so most of these settings (save-exact, prefer-dedupe, lockfile-version) are defense-in-depth / hygiene rather than primary defenses. The two settings that meaningfully change the security posture are ignore-scripts (blocks the install-script attack vector) and the future min-release-age (blocks freshly-published malicious releases before they're pulled). The rest aligns this repo with the rest of WordPress.

Use of AI Tools

Wrote the .npmrc and this PR description in collaboration with Claude Code (Opus 4.7). All technical choices were reviewed and decided by me; the diff is small and I verified each setting against npm docs and the upstream .npmrc files in wordpress-develop and gutenberg.

Sets `ignore-scripts = true` to block lifecycle scripts from dependencies,
and `save-exact = true` so new installs pin exact versions. Also includes
a commented-out `min-release-age = 7` to be enabled once the project's
minimum npm version is raised to 11+.

Because `ignore-scripts` also skips this repo's own `prepare` script,
contributors must now run `npm run prepare` once after install to wire up
husky. This is documented in the Performance Lab handbook.

Additionally aligns with the wordpress-develop and Gutenberg `.npmrc`
defaults: `engine-strict`, `legacy-peer-deps`, `lockfile-version = 3`,
and `prefer-dedupe`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: thelovekesh <thelovekesh@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@westonruter westonruter requested a review from thelovekesh May 14, 2026 18:01
@westonruter westonruter added [Type] Enhancement A suggestion for improvement of an existing feature no milestone PRs that do not have a defined milestone for release labels May 14, 2026
`engine-strict = true` correctly surfaced a latent inconsistency: the
recent Dependabot bump to `lint-staged@17.0.2` requires Node >= 22.22.1,
but `engines.node` is `>=20.19.0` and `.nvmrc` pins Node 20.19. CI
consequently fails to `npm ci`.

Comment it out for now with the same treatment as `min-release-age`:
re-enable once the project's minimum Node version is raised to match
current dep requirements (or the offending dep is downgraded).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread .npmrc Outdated
westonruter and others added 2 commits May 14, 2026 11:11
@westonruter westonruter merged commit 5184840 into trunk May 14, 2026
7 checks passed
@westonruter westonruter deleted the add/npmrc branch May 14, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no milestone PRs that do not have a defined milestone for release [Type] Enhancement A suggestion for improvement of an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants