Why a fresh-laptop `npm install` is your highest-risk moment
You wipe your laptop. New OS, fresh machine. You clone your project. You run npm install. The console scrolls for a minute. Everything goes green. You ship the feature you'd been working on.
That npm install you just ran is the highest-risk moment in your project's lifecycle.
Why fresh is risky
When you run npm install on a machine with a populated cache, npm pulls from ~/.npm/_cacache first. The tarballs that landed on your disk weeks or months ago are the ones it uses — assuming they match your lockfile's hashes.
When you run npm install on a fresh laptop, your cache is empty. Every single dependency in your tree is re-fetched from the live registry. Every. Single. One.
Including transitive deps you've never directly inspected. Including dependencies that were hijacked between your last install and this one. Including version specifiers that resolve to packages published yesterday.
Your existing project's lockfile mitigates this, but only partially.
What lockfiles do and don't protect against
package-lock.json (or yarn.lock, pnpm-lock.yaml) pins the exact versions of every package in your tree. With a lockfile, you get the same versions you had last time — not whatever the registry currently considers ^1.2.3.
Lockfiles also pin integrity hashes (for npm v2+ lockfiles, the integrity field). If a package's bytes change without its version changing, the hash mismatches and npm ci will refuse. npm install is more lenient and will sometimes auto-resolve.
What lockfiles don't protect against:
- Hijacked versions you don't yet have in the lockfile. If you're upgrading from
1.2.3to1.3.0because someone PR'd that, and1.3.0is a hijack, your fresh install picks it up. - Brand-new transitive deps. A direct dep updated and pulled in a new transitive that didn't exist when you last installed. The new transitive's hash is freshly recorded; you have no historical comparison.
- Cache poisoning. If your registry mirror or cache was compromised between your last install and this one, even pinned hashes get returned wrong (npm will catch the mismatch, but only if the lockfile is trusted).
- Time-of-check / time-of-use gaps. Lockfile says version 1.2.3 hash X. Registry serves a different binary for that same coord. npm catches it via hash; yarn in older configurations didn't always.
Specifically the vibe-coder failure mode
The shape of the risk in AI-assisted workflows:
- Lockfiles are inconsistent across your projects. Some of your repos have
package-lock.json, some don't, some are out of date, some were generated on a different OS that produced subtly different transitive trees. - You add new deps frequently. Your AI assistant suggests a library, you
npm installit, the lockfile updates. Each new dep is a fresh-from-registry pull with no history. - You re-scaffold projects. Throw away
node_modules, runnpm installagain. With a stale lockfile this is mostly safe. Without one, you're effectively at "first install" for the whole tree. - You clone projects you haven't touched in months. Same fresh-install risk — your laptop's cache is empty for those packages, the dependencies have moved on, and you're pulling whatever is current.
The mitigation that actually works for fresh installs
A gate at the install step is the only consistent answer because it's the only check that doesn't depend on prior state. The lockfile knows what you installed last time; the cache knows what you have on disk; nothing else knows whether the bytes you're about to pull are dangerous.
The gate scores every package every install. Fresh install on a new laptop? Every package gets re-scored. Most are fine (the gate has seen them before, the score is fast). Anything new — fresh slopsquat, recently-hijacked version, brand-new transitive — gets scored from scratch and refused if it fails.
This is more useful than a lockfile because it doesn't require you to have been here before. The first time you install a package on a fresh machine, the check runs.
Setup
Two commands once per machine (literally:install once and you're done):
veln onboarding
veln wrapper on
After that, every fresh install routes through the gate. The next time you clone a project on a new laptop and run npm install, every dependency is scored before the bytes hit disk. The malicious version that was published last Tuesday — that nobody in your cache yet has — gets refused.
The honest bit
The gate doesn't make npm install faster. (It's a few ms slower per package because of the score lookup, though cached scores return immediately.) It doesn't make fresh installs safer for every threat — a sophisticated long-game hijack that survived the cooling window can still slip through. It does make fresh installs no riskier than cached ones, which is the main thing.
If you do a lot of fresh-laptop installs — onboarding new devs, switching machines often, working across multiple VMs — this is the highest-leverage place to add a check. You're already going to type npm install. Make sure it scores every package, every time.