· 5 min read

Your Lockfile Is a Liability: How Attackers Exploit What You Don't Read

Kief Studio
Your Lockfile Is a Liability: How Attackers Exploit What You Don't Read

Your Lockfile Is a Liability: How Attackers Exploit What You Don't Read

You haven't opened package-lock.json in years. Neither has anyone on your team. GitHub collapses it by default in every PR review, and your brain learned to scroll past "12,847 changed lines" about six months into your career.

Attackers know this. They've been counting on it.

In 2025, 454,648 malicious npm packages got published. Over 99% of all open-source malware now targets npm. And the file you stopped reading a decade ago is the single fastest path into your build.

The file that isn't magic

Developers talk about lockfiles like they're a shield. "We're fine, we have a lockfile." The lockfile is not magic. It's a pinned install plan. Whether it protects you depends entirely on four things: is it committed, is CI using npm ci instead of npm install, are install scripts disabled, and does anyone actually read the diff when it changes.

Miss one of those, and the lockfile flips from defense to delivery mechanism.

The average npm project pulls in 79 transitive dependencies. Packages you never chose, authors you never vetted, code you never read. The lockfile is often the only place those transitive pins are visible, which means when an attacker wants to smuggle something in, the lockfile is exactly where they put it.

Axios, March 31, 2026

Three-hour window. Versions 1.14.1 and 0.30.4 published from a compromised maintainer account. Axios does ~100M weekly downloads.

The diff in package.json was one line: a new dependency called plain-crypto-js. That package didn't contain crypto helpers. It contained a cross-platform RAT with postinstall execution.

The malicious code was never in the Axios repository. It lived entirely in a transitive dependency, pulled in by a caret range any developer would sign off on without thinking:

{
  "dependencies": {
    "axios": "^1.14.0"
  }
}

Teams that ran npm ci against a committed lockfile from before the window were unaffected. Teams that ran npm install locally and let the lockfile silently rewrite itself, or teams that regenerated lockfiles in CI, shipped the RAT.

That's the whole story. Same package.json. Different lockfile. One team is fine, the other has a shell on every developer laptop.

Chalk and debug, September 2025

Eighteen packages. Combined weekly downloads: ~2.6B. Backdoored for about two and a half hours.

Attacker takeaway: roughly $20.

The reason the haul was so small is interesting. Most projects with committed lockfiles never regenerated during the window. The exposure wasn't "everyone who uses chalk." It was "everyone who ran a fresh install or CI regeneration inside a 150-minute slice of a Monday." The lockfile, when actually respected, clipped the blast radius by three orders of magnitude.

This is the thing no one says out loud: a committed lockfile plus npm ci is one of the single highest-leverage supply chain controls that exists. And most small shops don't use it.

Shai-Hulud V2, November 2025

Seven hundred plus packages infected. Zapier, Postman, PostHog, ENS, AsyncAPI. The worm created 22,000+ public GitHub repos full of exfiltrated enterprise secrets.

Two details matter:

  1. It shifted from postinstall to preinstall. Scanners looking for postinstall hooks missed it.
  2. It executed via the Bun runtime to evade Node-based detection.

Every major worm in the last 18 months used an install script as the trigger. --ignore-scripts in CI went from niche advice to the default recommendation. If you're not running it, you're letting arbitrary code execute on every CI build.

# In CI, always:
npm ci --ignore-scripts

# Or as a persistent default:
npm config set ignore-scripts true

Yes, this breaks packages that genuinely need post-install steps (node-gyp builds, native binaries). The fix is to allowlist those explicitly, not to leave the door open for everything.

The lockfile itself can be the attack

Here's the part that breaks most developers' mental model.

A malicious PR that changes only package-lock.json slips past review because GitHub, GitLab, and every other code host collapse auto-generated files by default. Change a resolved URL to point at an attacker-controlled mirror, update the integrity hash to match the malicious tarball, and the PR shows up in review as "lockfile updated after dependency bump." Reviewer clicks approve without expanding the file.

This isn't theoretical. SafeDep documented the technique under "lockfile poisoning." The attack surface is the human reading the PR, not the package registry.

And it gets worse for pnpm users. CVE-2025-69263 ("PackageGate"), disclosed by Koi Security's Oren Yomtov in January 2025, found that pnpm stored HTTP tarball and git-based dependencies in the lockfile without integrity hashes at all. A remote server could serve benign code during audit and malicious code during the next install. The lockfile provided zero protection because the lock had no hash to check against.

Even the tool meant to validate lockfiles had a hole. CVE-2025-4759 in lockfile-lint-api: append @evil/pkg to a legit package name and the URL validator accepts it. The defense tool was the attack surface.

What actually works

Boring, compounding controls. Not magic.

Commit the lockfile. Every project, every time. No exceptions for "this is just a library." If you publish a library, keep the lockfile out of the published tarball (.npmignore it), but commit it to the repo for reproducible dev and CI.

Use npm ci, not npm install, in CI. npm ci fails if package.json and the lockfile disagree. npm install silently rewrites the lockfile to whatever resolves at that moment. Guess which one was the delivery vehicle for the Axios RAT.

Disable install scripts in CI. npm ci --ignore-scripts. Every major 2025-2026 worm used preinstall or postinstall as the trigger. Cut off the execution path.

Pin and cool down. Tools like Aikido Safe Chain enforce a minimum package age (usually 24 hours) before install is allowed. Every major attack of the last 18 months was caught within hours. A one-day cooldown would have blocked all of them.

Actually read lockfile diffs on dependency PRs. Or automate it. Diff the resolved URLs and integrity hashes between old and new lockfiles, fail CI if anything resolves outside the expected registry. Scan the full tree, not just direct dependencies.

Scan the lockfile for known-bad packages before install. This is what Vekt does. Point it at your package-lock.json, pnpm-lock.yaml, yarn.lock, Cargo.lock, or any of the 22 lockfile formats it supports, and it checks every pinned version against OSV.dev for CVEs and MAL-* malicious package advisories:

vekt scan ./package-lock.json

First scan of the Kief Studio codebase came back with 221 lockfiles, 69,419 packages across 6 ecosystems. You cannot eyeball that. The tooling has to do it.

The uncomfortable part

Enterprises mostly get this right. Locked CI, npm ci, mandatory scanning, token rotation. GitHub disabling classic long-lived npm tokens in November 2025 was a direct response to Shai-Hulud using stolen NPM_TOKENs to republish packages, and fine-grained tokens with 2FA and expiration are now table stakes.

Solo devs and small agencies, on the other hand, run npm install on a laptop, commit the regenerated lockfile, push, and ship. A two-hour malicious window on a popular package disproportionately hits the teams who "just ran install yesterday" and didn't check what changed.

That's most of the industry. That's the exposure.

Takeaway

Open your lockfile. Right now. Look at it. Get over the size and read a few resolved URLs. Check that they all point at the registry you expect. Notice how many packages you've never heard of are pinned in there.

Then go make sure CI runs npm ci --ignore-scripts, not npm install, and that the lockfile is committed on every repo your team owns.

The attackers read your lockfile. You should too.

If you want an automated second pair of eyes, Vekt scans 22 lockfile formats against OSV.dev and flags malicious packages before they land in your build. Free tier, 50 scans a day, no signup to try it: kief.dev.

meta_title: "Your Lockfile Is a Liability: How Attackers Exploit It"
meta_description: "454k malicious npm packages in 2025. The Axios RAT slipped in via a lockfile no one read. Here's how to stop being the next easy win."
tags: ["supply-chain-security", "npm", "lockfile", "devsecops", "vekt"]