Add .npmrc with security hardening settings#462
Conversation
Security hardening: ignore-scripts prevents malicious install scripts from running during npm install, and save-exact pins dependency versions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a repository-level .npmrc to harden npm installs by disabling lifecycle scripts and pinning exact dependency versions, aligning with an org-wide supply-chain security initiative.
Changes:
- Add
.npmrcwithignore-scripts=trueto prevent lifecycle script execution during installs. - Add
.npmrcwithsave-exact=trueto pin exact dependency versions inpackage.json.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Code Review
This PR adds a .npmrc with supply-chain hardening settings and documents the resulting developer workflow change in the README; the intent is sound, but there is one important functional concern and a couple of minor issues worth addressing.
Inline comments: 4 discussions added
Overall Assessment:
| @@ -0,0 +1,2 @@ | |||
| ignore-scripts=true | |||
There was a problem hiding this comment.
Important Issue: ignore-scripts=true blocks all lifecycle scripts for all packages, including those that require them to function correctly after a fresh install. The most relevant example in this repo is esbuild (a devDependency declared in package.json), which uses a postinstall script to download the correct platform binary. With ignore-scripts=true, a fresh npm install on a new machine will produce a non-functional esbuild binary, and npm run bundle will fail silently or with a confusing error.
The PR description acknowledges this with the lifecycle-script-allowed[] workaround, but that syntax (lifecycle-script-allowed) is not a real npm config key — it does not exist in any npm version. The valid approaches are:
- Use
npm install --ignore-scripts=false esbuildafter initial install (manual, error-prone for new contributors) - Move
esbuildto use a wrapper package that doesn't rely on install scripts (e.g.,esbuild-wasm) - Use a
.npmrcper-package override via//registry.npmjs.org/:_authTokenstyle or a tool likepnpmthat has a proper allowlist - Accept the tradeoff and document it clearly, then require contributors to re-run
npm install --ignore-scripts=false esbuildwhen needed
At minimum, the README note should be expanded to mention that esbuild may need manual install if it's not already cached in node_modules.
| @@ -0,0 +1,2 @@ | |||
| ignore-scripts=true | |||
| save-exact=true | |||
There was a problem hiding this comment.
Suggestion: save-exact=true pins exact versions when running npm install <pkg> but has no effect on the already-committed package.json — existing entries like "esbuild": "^0.25.0" keep their caret ranges. This setting only changes behavior for future npm install <new-package> commands run by developers.
If the goal is truly reproducible builds, the existing package-lock.json already provides that guarantee — save-exact=true is complementary but not sufficient on its own to harden the existing entries. Consider whether the intent is:
- Enforcing exact pins for future additions (this setting does that — acceptable, just document it)
- Pinning all current entries (would require updating
package.jsonranges to exact versions — a separate, larger change)
This is a low-risk setting but the distinction should be made clear in the PR description or a follow-up comment.
| ## Development / npm commands | ||
|
|
||
| ```bash | ||
| npm run prepare # one-time after cloning: install husky git hooks (pre-push lint) |
There was a problem hiding this comment.
Suggestion: The comment says "one-time after cloning" but npm run prepare should also be re-run after npm ci in a clean CI-like environment (since ignore-scripts=true will suppress it). Consider rewording to make it clear this is required any time node_modules is re-created from scratch:
npm run prepare # run once after npm install (sets up husky git hooks)
Also, since this is a client-side browser library consumed by downstream projects, it is worth noting that the .npmrc will affect consumers who clone the repo, but ignore-scripts=true does not propagate to consumers who install the published npm package — so there is no downstream impact for library users.
| npm run docs # output documentation to `./docs` directory | ||
| ``` | ||
|
|
||
| > **Note:** `.npmrc` sets `ignore-scripts=true` for supply-chain safety, so lifecycle scripts (including husky's hook installation) do not run automatically on `npm install`. Run `npm run prepare` once after cloning to enable the `pre-push` lint hook. |
There was a problem hiding this comment.
Important Issue: This note only mentions husky, but omits the more impactful case: esbuild's binary setup also relies on a postinstall script. A new contributor following these instructions exactly (npm install then npm run prepare) will have a broken esbuild and will hit a confusing failure when running npm run bundle or any build step.
The note should either:
- Explicitly call out that
esbuildrequiresnpm install --ignore-scripts=false esbuildafter the initial install, OR - If
esbuildhappens to be bundled without its native binary in thenode_modulesalready committed (which would be unusual), clarify that.
Recommended expanded note:
> **Note:** `.npmrc` sets `ignore-scripts=true` for supply-chain safety. After `npm install`:
> 1. Run `npm run prepare` once to install the husky `pre-push` lint hook.
> 2. Run `npm install --ignore-scripts=false esbuild` if `npm run bundle` fails (esbuild requires its postinstall script to download the platform binary).
Summary
.npmrcwithignore-scripts=trueandsave-exact=trueignore-scripts=trueprevents arbitrary code execution duringnpm install(supply chain defense)save-exact=truepins exact dependency versions for reproducible buildsIf a package requires install scripts
Add a per-package allowlist in
.npmrc:Or run one-off with:
Context
Part of an org-wide security hardening initiative for all npm-based repositories.