DEV Community

Vigoss Luke
Vigoss Luke

Posted on

npm v12 Is Landing in July — Here's Exactly What Will Break and How to Fix It Now

TL;DR: npm v12 flips three defaults that will silently break your CI: install scripts are off, Git deps are blocked, remote tarballs are blocked. Your npm ci can exit 0 while native modules were never compiled. Here's a copy-paste migration plan.

───

The Big Picture

For 12 years, npm has let any package in your dependency tree run arbitrary code during npm install. A single compromised package with a postinstall script could exfiltrate your environment variables, SSH keys, and .npmrc tokens. The Shai Halud supply-chain attack made it impossible to ignore.

npm v12 finally fixes this. But the fix comes with pain:

Scripts are off by default — allowScripts defaults to off. Native modules (bcrypt, sharp, esbuild, node-gyp) won't compile.

Git dependencies are blocked — --allow-git defaults to none. Monorepo internal refs, forks, private repo deps — all blocked.

Remote URL deps are blocked — --allow-remote defaults to none.

You will get v12 whether you want it or not. Node.js bundles npm. When LTS releases (22, 24, 26) pick up the v12 bundle, your CI pipelines and dev environments will suddenly have different behavior.

Good news: npm 11.16.0+ already shows warnings. You can prepare today.

───

The Silent Killer: npm ci Can Succeed While Your App Is Broken

This is the most dangerous part of v12, and it's catching teams off guard:

$ npm ci # exits with code 0 ✅
$ npm test # passes ✅
$ node app.js # 💥 Cannot find module 'bcrypt'

npm v12's npm ci doesn't fail when scripts are blocked — it just silently skips them. Your native modules were never compiled, but CI went green. The app crashes at runtime.

Fix: Add a smoke test after every npm ci:

node -e "
const pkgs = ['sharp', 'esbuild', 'bcrypt'];
for (const p of pkgs) {
try { require(p); console.log('✓', p); }
catch(e) { console.error('✗', p, '— NOT COMPILED'); process.exit(1); }
}
console.log('All native modules present');
"

───

Migration Plan (15 Minutes)

Step 1: Upgrade to npm 11.16.0+ (warning mode, nothing breaks yet)

npm install -g npm@11

Step 2: See what will be blocked

npm install

You'll see warnings like:

npm warn allow-scripts 8 packages have install scripts not yet covered
npm warn allow-scripts Run npm approve-scripts --allow-scripts-pending to review

Step 3: Audit and approve

List every package that runs scripts

npm approve-scripts --allow-scripts-pending

Approve the ones you trust (most projects have 3-8)

npm approve-scripts bcrypt sharp node-gyp

Deny the rest (writes to package.json — commit this)

npm deny-scripts

Step 4: Find Git dependencies hiding in your tree

grep -r "git+" package.json package-lock.json
npm ls --all | grep -E "(git+|https://.*.tgz)"

You might be surprised — transitive dependencies often pull in Git refs. If you find any:

Quick fix: add to .npmrc

echo "allow-git=true" >> .npmrc

Better fix: migrate to registry versions

Step 5: Check for npm-shrinkwrap.json (it's gone in v12)

ls npm-shrinkwrap.json 2>/dev/null && mv npm-shrinkwrap.json package-lock.json

Step 6: Fix npm view --json parsing

v12 changes npm view --json to always return arrays. If your CI scripts do:

npm view lodash version --json | jq -r '.version'

This will break. Fix:

npm view lodash version --json | jq -r '.[0]'

───

GitHub Actions: Production-Ready Workflow

Here's a complete CI workflow for npm v12:

name: CI — npm v12 Compatible

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: [18, 20, 22, 24]

steps:
  - uses: actions/checkout@v4
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node }}

  # Pin npm 12 for forward-compatibility testing
  - run: npm install -g npm@12

  - uses: actions/cache@v4
    with:
      path: ~/.npm
      key: npm-${{ runner.os }}-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}

  # Install with scripts pending, then approve known-safe ones
  - run: npm ci --allow-scripts-pending
  - run: npm approve-scripts node-gyp sharp esbuild bcrypt

  # SMOKE TEST: verify native modules actually compiled
  - run: |
      node -e "
      const pkgs = ['sharp', 'esbuild', 'bcrypt'];
      for (const p of pkgs) {
        try { require(p); console.log('✓', p); }
        catch(e) { console.error('✗', p, '— NOT COMPILED'); process.exit(1); }
      }
      "

  - run: npm test
Enter fullscreen mode Exit fullscreen mode

───

Docker: Multi-Stage with C++17

npm v12 requires C++17 for native modules. Your old Docker image with gcc 7 won't cut it.

FROM node:22-slim

npm v12 needs C++17 toolchain

RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential python3 make gcc g++ \
&& rm -rf /var/lib/apt/lists/*

RUN npm install -g npm@12

WORKDIR /app
COPY package.json package-lock.json ./

Install + approve + verify

RUN npm ci --allow-scripts-pending && \
npm approve-scripts node-gyp sharp esbuild && \
node -e "require('sharp'); require('esbuild'); console.log('Native modules OK')"

COPY . .
EXPOSE 3000
CMD ["node", "index.js"]

───

Should You Upgrade Now?

Scenario Action
Pure JS project (no native deps) Upgrade now — 5 minute fix
Has native deps (bcrypt, sharp, etc.) Run the audit on npm 11.16.0+ this week, upgrade before July
Monorepo with Git deps 1 hour audit — find and migrate Git refs first
In the middle of a critical release Stay on npm 11, but run the audit so you're ready

───

Bottom Line

npm v12 isn't a "should I upgrade?" question. It's a "when will it hit me?" question. Node.js will bundle it, your CI will inherit it, and your deployment will break at the worst possible time — unless you prepare now.

The migration takes 15 minutes for most projects. The outage it prevents is worth 15 hours.

───

This guide is based on npm 11.16.0+ behavior and the official npm v12 announcement. For more details, check out InstallSafe.dev — a free npm v12 migration guide with FAQ, CI/CD templates, and a side-by-side npm 11 vs 12 comparison.

Top comments (0)