npmvulncheck is a govulncheck-inspired vulnerability scanner for npm projects.
It combines lockfile/installed dependency analysis with optional source reachability to help reduce noisy findings.
- Uses OSV as the vulnerability source (
/v1/querybatch,/v1/vulns/{id}) - Supports three scan modes:
lockfile,installed,source - Supports
package-lock.json/npm-shrinkwrap.json/pnpm-lock.yaml/yarn.lockin lockfile-based modes - Understands JS/TS
import,require, and literal dynamicimport(...) - Resolves imports from each containing workspace/package context (not only root)
- Supports
text,json,sarif, andopenvexoutputs - CI-friendly exit code control (
--exit-code-on,--fail-on,--severity-threshold) - Includes local cache support and offline scanning
- Node.js
>=18 - Node.js project with one of:
package-lock.json/npm-shrinkwrap.jsonpnpm-lock.yamlyarn.lock
node_modulesinstalled forinstalledmode (npm tree only)
npm install -g npmvulncheckOr run without global install:
npx npmvulncheck --helpnpm install
npm run build
npm linkdocker run --rm -v "$PWD:/work" shodohq/npmvulncheck:latest --mode lockfile --format text# Default scan (lockfile + text)
npmvulncheck
# Installed tree scan
npmvulncheck --mode installed --format text
# Source reachability scan
npmvulncheck --mode source --entry src/index.ts --show traces
# Machine-readable output
npmvulncheck --mode source --format json > findings.json| Mode | Input graph | When to use | Notes |
|---|---|---|---|
lockfile |
lockfile dependency graph | Fast, deterministic CI scans | Supports npm/pnpm/yarn lockfiles |
installed |
actual node_modules tree |
Match what is actually installed | npm installed tree only |
source |
lockfile + source imports | Prioritize reachable findings | Keeps non-reachable findings at lower priority; unresolved imports remain unknown |
You can pass explicit entry files with repeatable --entry.
If no valid entries are provided, entries are auto-discovered from:
package.jsonfields (main,bin,exports)- Common conventions (
src/index.ts,src/index.js,index.ts,index.js, etc.)
# Scan
npmvulncheck [options]
# Scan with remediation planning strategy
npmvulncheck --strategy auto
npmvulncheck --strategy override --format sarif
# Show vulnerability detail
npmvulncheck explain GHSA-xxxx-xxxx-xxxx
# Show tool/db cache metadata
npmvulncheck version--mode lockfile|installed|source--format text|json|sarif|openvex--strategy override|direct|in-place|auto(default:auto)--scope global|by-parent--upgrade-level patch|minor|major|any--only-reachable/--include-unreachable(remediation planning filter)--root <dir>--entry <file>(repeatable)--conditions <condition>(repeatable; source-mode module conditions override)--include-type-imports(includeimport type/export typein source reachability)--explain-resolve(include unresolved import diagnostics and resolution candidates)--show traces|verbose--include dev/--omit dev(default: omit dev)--include-dev/--omit-dev--cache-dir <dir>--offline--ignore-file <path>--exit-code-on none|findings|reachable-findings--fail-on all|reachable|direct--severity-threshold low|medium|high|critical
Remediation planning runs as part of the default scan flow. The selected strategy controls how remediation actions are generated:
override: transitive dependency overridesdirect: direct dependency upgradesauto: direct + transitive candidatesin-place: alias ofauto(kept for compatibility)
Output mapping:
sarif: remediation actions are emitted in each result'sfixespropertyjson: remediation plan is emitted as top-levelremediation, and per-affected notes are merged intofindings[].affected[].fix.noteopenvex: remediation actions are emitted instatements[].action_statementtext: remediation actions are shown in each finding'sfix:line
Priority mapping:
text: each finding header includespriority:<level>json: each finding includesfindings[].prioritysarif: each rule/result includesproperties.priority_level,properties.priority_reason,properties.priority_scoreopenvex: each statement includesstatus_noteswith priority/reason/score
Default behavior depends on output format:
text: default--exit-code-on findings(exit1when filtered findings exist)json/sarif/openvex: default--exit-code-on none(exit0unless runtime error)
Examples:
# Fail CI only for reachable vulnerabilities with severity >= high
npmvulncheck \
--mode source \
--format json \
--exit-code-on reachable-findings \
--fail-on reachable \
--severity-threshold highDefault file: .npmvulncheck-ignore.json at project root.
{
"ignore": [
{
"id": "GHSA-xxxx-xxxx-xxxx",
"until": "2026-06-30",
"reason": "Waiting for upstream patch"
}
]
}Notes:
- Rules are matched by vulnerability
id - Expired rules are ignored
- Invalid
untilvalues are ignored
npmvulncheck caches vulnerability details and can run offline.
# Warm cache (online)
npmvulncheck --mode lockfile
# Reuse cache only
npmvulncheck --mode lockfile --offlineUse --cache-dir <dir> to override the cache location.
examples/guided-remediation demonstrates transitive remediation flow (override) and can also be run with auto.
# override strategy
npmvulncheck --root examples/guided-remediation --strategy override --format text
# auto strategy (direct + transitive)
npmvulncheck --root examples/guided-remediation --strategy auto --format textexamples/complex-unused-deps demonstrates how source mode can prioritize dependencies that are reachable from your entrypoint while lowering the priority of non-reachable findings.
npmvulncheck --root examples/complex-unused-deps --mode lockfile --format text
npmvulncheck --root examples/complex-unused-deps --mode source --entry src/index.ts --show traces --format textNote: this fixture is for reachability scans, not remediation workflow coverage. --strategy override may produce a no-op plan when findings are direct dependencies; use --strategy direct or --strategy auto to include direct upgrades.
npm install
npm run lint
npm test
npm run buildGitHub Actions workflows are configured in .github/workflows.
ci.yml: runs on everypushandpull_requestwith Node.js18,20, and22- Steps:
npm ci->npm run lint->npm test->npm run build
- Steps:
scorecards.yml: runs OpenSSF Scorecard onmainpush and weekly schedule, then uploads SARIF to code scanningcd.yml: runs onv*tag push and publishes to npm and Docker Hub after lint/test/build pass- Includes a guard that checks
vX.Y.Ztag matchespackage.jsonversion - Uses
npm publish --provenance --access public - Builds and pushes multi-arch image tags (
X.Y.Z,X.Y,latest)
- Includes a guard that checks
Set these secrets in GitHub repository settings:
NPM_TOKEN: npm automation token with publish permissionDOCKERHUB_USERNAME: Docker Hub username (or organization bot user)DOCKERHUB_TOKEN: Docker Hub access token
If you publish under a different Docker Hub repository name, update DOCKER_IMAGE in .github/workflows/cd.yml.
# 1) bump version
npm version patch
# 2) push commit and tag
git push origin main --follow-tagsWhen the v* tag is pushed, the CD workflow publishes the package automatically.
The same workflow also pushes Docker images and creates a GitHub Release with generated notes.
See CONTRIBUTING.md for setup, PR checklist, and review expectations.
AGPL-3.0-only (see LICENSE)