PentStark
Blog · AppSec

Lockfile injection: the npm supply-chain attack your SCA missed

PentStark Product SecurityMarch 4, 20267 min readMore on AppSec

"Commit your lockfile" has been the standard npm supply-chain advice for a decade. It's necessary. It's also not sufficient. Attackers across 2025 and into 2026 have been slipping malicious packages past SCA tools by targeting the lockfile itself — not the package.json, not the registry, but the artifact everyone has been telling you to trust.

The shape of the attack

In short:

  1. Attacker gets a PR merged that touches the lockfile (package-lock.json, yarn.lock, or pnpm-lock.yaml).
  2. The PR looks innocuous: "chore: update dependencies", "fix: align lockfile with package.json".
  3. The diff includes a resolved URL change for one transitive dependency — pointing to a look-alike registry or a pinned tarball the attacker controls.
  4. CI runs npm ci, which honors the lockfile's resolved URL, pulls the attacker's tarball, and builds it into the production artifact.
  5. SCA scans the package.json and sees the same top-level versions. Everything "looks clean."

The attack works because SCA tools have historically treated the lockfile as ground truth for what's installed but haven't validated that what's resolved matches the declared registry.

The primitives

Resolved-URL swap

In package-lock.json, every package has a resolved field:

"node_modules/lodash": {
  "version": "4.17.21",
  "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
  "integrity": "sha512-..."
}

Swap the resolved URL to https://registry.attacker.example/lodash-4.17.21.tgz. Compute an integrity hash for the malicious tarball. Commit. npm ci will install from the attacker's URL with the matching integrity.

Integrity subresource poisoning

Some lockfiles include an integrity field but not resolved. Depending on the client and config, the package manager may fall back to a different registry if the first one 404s. Attacker 404s the legitimate path and serves a matching-integrity tarball from theirs.

Lockfile drift via merge

Attacker opens a PR. CI runs npm install — not npm ci — on the PR branch to "re-resolve" the lockfile. The install pulls from the attacker's registry due to a crafted .npmrc in the PR. The regenerated lockfile now contains the attacker's resolved URLs. When the PR merges, those URLs are the new ground truth.

What SCA actually checks

Most SCA tools through mid-2025 checked:

  • Top-level dependency names and versions against CVE databases.
  • License compliance.
  • Occasionally the installed node_modules tree structure.

Most did not, until recently, check:

  • That resolved URLs in the lockfile point to the declared registry.
  • That integrity values match what the registry actually serves.
  • That CI hasn't regenerated the lockfile on a branch where .npmrc was modified.

The tools have been catching up. Not all of them. Verify yours.

What to check this quarter

  1. Registry URL policy. Grep your lockfiles for resolved fields. Every URL should match your expected registry (registry.npmjs.org or your internal mirror). Automate this as a CI check that fails the build on drift.
  1. .npmrc provenance. An .npmrc in the repo, in a workspace, or set via CI env var can redirect installs. Inventory every .npmrc the build sees. Any PR that touches one is a high-risk diff.
  1. Install vs. CI. Production builds should use npm ci (or yarn --frozen-lockfile, or pnpm install --frozen-lockfile) — never plain install. The bare install command is permitted to regenerate the lockfile. CI is not the place to regenerate it.
  1. Integrity validation on the registry side. If you use a proxy (Artifactory, Verdaccio, GitHub Packages), confirm it validates upstream integrity. Some proxies cache whatever was served; a race in the cache can make a swap persist.
  1. PR diff gating. Any PR whose diff touches a lockfile's resolved URL or integrity field should require a maintainer review plus a registry-policy CI check.

The mental-model shift

The lockfile is input to your build, not a fixed artifact. It's code. It's attacker-reachable in the same ways other code is. Treat it with the same review discipline as your Dockerfile or your CI config.

"Commit your lockfile" was good advice. "Verify your lockfile, pin your registry, and gate your CI re-resolves" is the 2026 advice.

Get research like this monthly.

No marketing fluff. Unsubscribe anytime.

Talk to an operator

Your next finding is one scoping call away.

Thirty minutes with a real operator tells us what you need and what we can deliver. No BDR handoff, no sales engineer theater — the person you talk to is the person who scopes the work.

Talk to an expertBook a demo
Responses in < 1 business day