You Approved That npm Package. Do You Have Any Idea What It Does?

7 min read

By Tiago Basilio

Last week you ran something like npm approve-scripts fsevents, saw the install go green, and moved on. Be honest: you have no idea what fsevents actually does when it installs. Neither do I. Nobody on your team has read its source, and nobody ever will.

That sounds reckless. It isn’t, as long as you understand one thing: trusting a package was never about reading its code. It is about reading the signals around it.

This is the sequel to npm flipping its install-script default to deny. That change hands you a new job: a short list of dependencies asking permission to run code, and a decision to make on each one. So how do you actually make that call when you cannot audit what you are approving? Here is the realistic checklist, from a five-second gut call to a cryptographic proof.

First, Kill the Fantasy of Reading the Code

When fsevents installs, it compiles native C++ that talks to macOS file-system APIs. Reading that and judging it safe requires fluency in C++, node-gyp, and the macOS kernel interfaces it binds to. Almost no one has that. The senior engineer next to you does not have it either.

Now multiply by reality. A normal project has hundreds to thousands of transitive dependencies. Even if you could audit one, you cannot audit a thousand, and you certainly cannot re-audit them every time one publishes an update.

So drop the guilt. “Just read the code” does not scale and never did. What scales is a system of signals that lets you decide quickly and revisit cheaply. That system is what professional teams actually rely on, whether they admit it or not.

The Trust Ladder

Work down this list. Most decisions are made on the first rung. You only go deeper when something feels off.

1. Reputation and Ubiquity (Your Strongest Signal)

A package downloaded millions of times a week, depended on by half the ecosystem, has thousands of expert eyes on it and an army of security researchers poking at it. Malicious code in that kind of package gets caught fast, often within hours. That crowd is doing the audit you cannot.

fsevents is exactly this category. It is an optional dependency pulled in by Vite, Vitest, and nearly every macOS JavaScript toolchain. If it shipped something malicious, the blast radius would be enormous and the discovery near-instant.

A quick look before you approve:

npm view fsevents

That shows you the maintainers, the linked repository, the homepage, and when it was last published. Cross-check the download count and dependent-package count on the package’s npmjs.com page. The shape you want is boring: long history, many maintainers, millions of downloads, thousands of dependents.

The opposite shape is the warning. A brand-new package with twelve downloads, no repository link, published three days ago, suddenly asking to run an install script: that is exactly where you stop and look harder.

2. Provenance and Signatures (Cryptographic Proof of Origin)

Reputation tells you a package is widely trusted. Provenance tells you the thing you downloaded is genuinely the thing that was published, from the source repo it claims, built by CI, and not tampered with in transit or swapped out by a hijacked account.

npm can verify this for your whole tree:

npm audit signatures

This checks the registry signatures on your installed packages and, where available, their provenance attestations. Provenance links a published package back to the exact commit and CI run that built it, using a signed, tamper-evident record. It does not prove the code is friendly. It proves nobody slipped something in between the source you can see and the artifact you installed, which closes off a whole class of supply-chain attack.

3. Automated Scanners (Outsourced Expertise)

This is the closest thing to having a security expert review the package for you.

  • Socket specifically inspects what a package does: whether it runs install scripts, opens network connections, touches the filesystem, reads environment variables, or contains obfuscated code. It flags the exact behaviors that matter for the approve-or-deny decision you are making, and it does it on every dependency and every update.
  • npm audit checks your tree against a database of known vulnerabilities. That is not the same as detecting malice, a package can be malicious and “clean” on npm audit, but it catches the large category of known-bad versions, and it is one command:
npm audit

Run Socket on pull requests and npm audit in CI, and you have a machine doing the behavioral review no human on your team has time for.

If you are maintaining one project, that is enough. If you are maintaining many, running these engines across a whole portfolio becomes its own job. That is the itch I built TedGuard to scratch: a self-hosted dashboard that runs Trivy, OSV-Scanner, Grype, and Gitleaks over every repo, dedupes the findings, and tracks remediation, without handing a SaaS read access to your code.

Mitigations: Lowering Risk When You Still Cannot Be Sure

Signals reduce uncertainty. They never eliminate it. The point is not to reach certainty, it is to stack cheap defenses so that no single compromise gets a free ride. These are the layers worth having regardless of how trustworthy a package looks.

  • Keep the allowlist tight. The new default-deny is itself your biggest mitigation. Approve only the packages that genuinely need install scripts, and leave the rest blocked. When in doubt, --ignore-scripts (or ignore-scripts=true in .npmrc) is the blunt hammer that turns all of them off.
  • Pin and commit your lockfile, and use npm ci. A committed package-lock.json plus npm ci means every install resolves to the exact versions you reviewed, not whatever floated in since. No surprise upgrades behind your back.
  • Add a cooldown before adopting new versions. Most malicious releases are caught and pulled within hours to days of publishing. npm added this natively in 11.10.0, so npm config set min-release-age 7 refuses any version published fewer than seven days ago. Renovate (minimumReleaseAge) and pnpm (minimumReleaseAge) give you the same gate. Let the ecosystem absorb the risk before you do, and never install a version that was published twenty minutes ago.
  • Automate updates through review. Renovate or Dependabot turn dependency bumps into pull requests you can actually look at, run scanners against, and merge deliberately, instead of npm update quietly moving everything at once.
  • Trust your gut on the small stuff. A package with no repo, a single anonymous maintainer, a name suspiciously close to a popular one, or a fresh publish date and an install script is telling you something. You do not need to read the code to walk away.

The Bottom Line

You cannot personally verify that fsevents, or any of your other dependencies, is free of malicious code. That is not a personal failing. It is the nature of building on a supply chain made of other people’s work.

What you can do is stop pretending trust comes from reading code, and start treating it as what it actually is: a stack of signals and cheap defenses. Reputation does most of the work. Provenance proves origin. Scanners catch behavior. Cooldowns, pinning, and a tight allowlist make sure that when something does slip through somewhere in the world, it does not slip into your build.

npm’s default-deny is the foundation. Everything above is the rest of the wall. You are not auditing your dependencies. You are making sure no single one of them can hurt you on its own.