npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kushankurdas/npm-sentinel

v0.1.4

Published

Static gate (lockfile + OSV) and isolated Docker npm install with DNS allowlisting

Downloads

142

Readme

npm-sentinel

npm-sentinel is built for the gap after “nothing matched CVE/NVD/OSV today.” Most scanners answer: is this version on a list? This tool asks: did the dependency graph or install behavior change in a way that should make me stop and think?

Think of it a little like macOS asking for consent when something new wants access: you still install packages with npm, but npm-sentinel surfaces differentials—what showed up that was not there before, or what the install tried to talk to—so you can react before “unknown” becomes “incident write-up.”

What you get:

  • Baseline drift — for packages you already trust (your direct deps), flag new transitive dependencies, new lifecycle scripts, and version jumps—the same class of signal as when a widely used client suddenly pulls in an unrelated helper package you did not expect.
  • Optional Docker sandbox — run npm ci with scripts inside a container and watch DNS against an allowlist, so you can see unexpected outbound hosts during install without running that install on your host.
  • Lockfile intelligence — optional OSV and offline IOC-style checks for known bad versions (useful, but explicitly not the whole story).

Why this is different from “CVE-only” tooling

| Typical dependency scanners | npm-sentinel’s angle | |------------------------------|----------------------| | Depend on curated feeds (CVE, NVD, OSV, vendor advisories) | Drift vs your own baseline — “this trusted package’s tree changed” | | Tell you after a CVE exists | Helps surface sketchy structure and behavior even when the vuln is still unnamed | | Rarely model “why does this package need that now?” | Highlights new edges in the graph and new install-time behavior |

OSV and similar feeds are one layer here. The baseline and sandbox layers are for when the threat is not yet in any database—or never will be, because the issue is “wrong package, wrong script, wrong phone home,” not a scored CVE.


Example outcomes (the mental model)

  • Static / baseline:axios’s resolved tree now includes plain-crypto-js (or another dependency you do not recognize). Nothing is ‘CVE-critical’ yet—but why is it there suddenly?
  • Sandbox: “During npm ci, something resolved from your lockfile tried to reach example-malicious-host[.]com (or any host not on your allowlist).” You configure what “normal” looks like; anything else is surfaced for review.

Those are differential signals: compare to what you accepted before and what the install actually did in isolation.


Simple guide: two jobs, five commands

Think of it as two layers:

| Layer | What it does | Needs Docker? | |--------|----------------|---------------| | A — Static | Reads the lockfile; optional OSV/IOCs; optional baseline compare (“what drifted?”) | No | | B — Sandbox | Runs a real npm ci in Linux + watches DNS | Yes |

Cheat sheet (what to run)

| I want… | Command | |--------|---------| | Quick “is my lockfile bad?” (daily / CI) | npx npm-sentinel check | | Same + compare to a saved “good” snapshot | npx npm-sentinel check --baseline | | Save a “good” snapshot (commit the file afterward) | npx npm-sentinel baseline save | | See what changed vs that snapshot | npx npm-sentinel baseline diff | | Heavy: install in Docker + DNS allowlist | npx npm-sentinel sandbox | | CI: static check, then optionally sandbox | npx npm-sentinel gate or npx npm-sentinel gate --require-sandbox |

Most teams: use check (and check --baseline once baselines exist) on every PR; add sandbox where you need install-time behavior proof (DNS, scripts) without trusting the host.

Commands in plain English

  • check — “Does this lockfile match known bad versions (OSV/IOCs)?” Optionally: “Did my trusted direct deps drift vs my baseline?”
  • baseline save — “Remember today’s dependency + script picture for my root package.json deps as trusted.” Writes .npm-sentinel-baseline.json.
  • baseline diff — “What changed since baseline save?” (new child deps, new install scripts, version bumps, etc.)
  • sandbox — “Copy the project into a container, run npm ci with scripts on, record DNS. Fail if DNS hits unknown domains or npm ci fails.” Use --mount-ssh if you have private git+ssh dependencies (see below).
  • gate — “Run check with baseline diff enabled if a baseline file exists. With --require-sandbox, run sandbox after check passes.”

Quick start

1. In any repo that has package-lock.json

cd your-project
npx npm-sentinel check

Exit code 0 = no findings at your severity threshold; 1 = findings or baseline errors.

2. Optional: block install on bad lockfile (host only runs static check)

Use npx or a global install so preinstall works on a fresh clone:

{
  "scripts": {
    "preinstall": "npx --yes npm-sentinel@latest check --baseline"
  }
}

Static preinstall does not run dependency postinstall on the host if npm aborts first. For install-time behavior, use sandbox separately or in CI.

3. Optional: baseline workflow

npx npm-sentinel baseline save
git add .npm-sentinel-baseline.json
git commit -m "chore: npm-sentinel baseline"

Later:

npx npm-sentinel check --baseline

4. Optional: Docker sandbox (git+ssh private deps)

npx npm-sentinel sandbox --mount-ssh

Or a folder with only deploy keys:

npx npm-sentinel sandbox --ssh-dir /path/to/keys

See Docker & SSH below for macOS vs Linux caveats.


Install

npm install -D npm-sentinel
npm install -g npm-sentinel

From a local clone of this repo:

cd npm-sentinel
npm link
cd /path/to/your-app
npm link npm-sentinel

Requirements

  • Node.js ≥ 18
  • Docker — only for sandbox and gate --require-sandbox
  • package-lock.json at the project root (required for check, baseline, sandbox)

Command reference (detailed)

| Command | Description | |--------|-------------| | npm-sentinel check | Lockfile → OSV + offline IOCs; add --baseline if .npm-sentinel-baseline.json exists | | npm-sentinel baseline save | Write baseline from lockfile + registry metadata for watched packages | | npm-sentinel baseline diff | Print drift vs baseline | | npm-sentinel sandbox | Docker: copy project, npm ci, tcpdump DNS vs allowlist | | npm-sentinel gate | check with baseline diff; --require-sandbox runs sandbox after | | npm-sentinel help | Print help |

Flags

| Flag | Purpose | |------|---------| | --cwd <dir> | Project root (default: current directory) | | --json | JSON output | | --min-severity | low | moderate | high | critical (default: low) | | --no-osv | Skip OSV API | | --offline | Skip OSV (offline IOCs still apply) | | --baseline | With check, run baseline diff when baseline file exists | | --npm-audit | Also run npm audit --json | | --require-sandbox | With gate, run sandbox after check | | --no-build | sandbox: reuse existing Docker image | | --mount-ssh | sandbox: mount host ~/.ssh read-only at /root/.ssh | | --ssh-dir <path> | sandbox: mount this directory instead of ~/.ssh | | --baseline-file <path> | Alternate baseline file path |


Docker sandbox and your machine

  • The sandbox runs Linux in Docker. It does not always give you a macOS/Windows–usable node_modules when packages ship native binaries built for your host OS.
  • Recommendation: run sandbox in CI (Linux); locally use check, or develop inside a Dev Container if you want Linux node_modules daily.

Private git+ssh dependencies

The image includes git and openssh-client.

| Approach | Command / config | |----------|------------------| | Mount host keys | npm-sentinel sandbox --mount-ssh | | Mount a key folder only | npm-sentinel sandbox --ssh-dir /path/to/keys | | Config file | npm-sentinel.config.json: "sandbox": { "mountSsh": true } or "sshDir": "/path" |

macOS: Your ~/.ssh/config may use UseKeychain, which Linux OpenSSH does not support. The tool sets GIT_SSH_COMMAND with -F /dev/null so the mounted config is ignored; default key files in the mount still apply (id_ed25519, id_rsa, …). Keys that exist only in the Keychain and not on disk may still fail — use a deploy key file or git+https with a token in CI.

Security: Mounting ~/.ssh gives the container read access to whatever keys are in that directory. Prefer deploy keys or HTTPS + token for automation.


Configuration

Create npm-sentinel.config.json or .npm-sentinelsrc.json in the project root:

{
  "watchPackagesExtra": ["some-transitive-parent"],
  "watchPackagesOverride": null,
  "dnsAllowlist": {
    "mode": "merge",
    "suffixes": ["my-registry.example.com"],
    "exactHosts": []
  },
  "sandbox": {
    "mountSsh": true
  }
}

| Key | Meaning | |-----|---------| | Watched packages | Defaults to root dependencies + devDependencies (+ optional peers / optionals). watchPackagesExtra adds more parent names. | | watchPackagesOverride | If set (array), only these names are watched (replaces default list + extras). | | dnsAllowlist | DNS allowlist for sandbox: mode merge (default) adds suffixes / exactHosts to the built-in list; replace uses only your lists (strict; you must include every host your install needs). | | sandbox.mountSsh | Same as --mount-ssh. | | sandbox.sshDir | Same as --ssh-dir (wins over mountSsh when set). |


Baseline signals

When you compare to a saved baseline, npm-sentinel can report:

  • New direct dependencies on a watched package (dependency injection)
  • New lifecycle scripts on a resolved version (preinstall / install / postinstall)
  • Version changes on watched packages
  • Removed dependencies (warning; can be noisy; possible account-takeover signal)

Real-world context: the Axios supply-chain write-up is exactly the kind of change a baseline diff is meant to make obvious early.


Development (this repo)

Command details and testing: docs/ (per-command guides under docs/commands/). Security reports: SECURITY.md.

git clone https://github.com/kushankurdas/npm-sentinel.git
cd npm-sentinel
npm ci
npm test

If your GitHub username or repo name differs, update the repository, bugs, and homepage fields in package.json to match.


Related reading


License

MIT