Skip to content

About

Catch risky npm dependency changes before they merge.

Topics

Resources

Contributing

Security policy

Stars

374 stars

Watchers

0 watching

Forks

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Latest commit

聽

History

67 Commits

Folders and files

Repository files navigation

npmscan logo

npmscan Dependency Check

Catch risky npm dependency changes before they merge.
No API key 路 No account 路 Free, including private repos

test OpenSSF Scorecard GitHub Marketplace MIT License

馃摉 Documentation

Official integrations: ChatGPT 路 Claude

Listed in ChatGPT and Anthropic's official Claude directory via the npmscan MCP integration.


Every pull request that changes package.json or a lockfile is checked for:

  • Known vulnerabilities and known malware from OSV.dev and GitHub Advisories
  • New install scripts: a dependency that suddenly runs code on npm install, the usual sign of a hijacked package
  • Repointed lockfile entries: the same version now downloading a different tarball, an entry whose tarball is actually a different package, or an upgrade that switches to an unknown server. CVE scanners don't check for this.
  • Changes to your own install scripts and overrides, which can run code or force a version without touching the dependency list

npmscan posts one PR comment, marks each finding on its line in Files changed, and fails the check when something meets your thresholds.

Quick start

Add .github/workflows/npmscan.yml:

name: npmscan
on:
  pull_request:
    paths: [package.json, package-lock.json, yarn.lock, pnpm-lock.yaml]
permissions:
  contents: read
  pull-requests: write
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: npmscan/npmscan-action@v1

That's it. Using pnpm or Yarn? Add with: { file: pnpm-lock.yaml } (or yarn.lock). Want to try it before it blocks anything? Add with: { mode: warn }.

What the PR comment looks like

A real comment from a PR that downgrades lodash to a vulnerable version (here in mode: warn):

npmscan PR comment flagging lodash 4.17.15 with a HIGH severity vulnerability

The comment is edited in place on every push, and the same report goes to the job summary.

What it flags

Finding Blocks by default To stop it blocking
Known malware (a malicious-package advisory) always only mode: warn
Known vulnerability any severity fail-on-severity: high (or critical, none)
A dependency gains a preinstall / install / postinstall script, or a new dependency has one yes fail-on-install-script: false
Your own root preinstall / install / postinstall / prepare script is added or changed yes fail-on-install-script: false
The same version now resolves to a different tarball URL or integrity hash, the tarball is a different package or version than the lockfile declares, or an upgrade moves to a different host yes fail-on-source-change: false
An overrides / resolutions entry is added or changed yes fail-on-source-change: false

Findings below your thresholds are still reported. They just don't fail the check. Ordinary upgrades (new version, new tarball from the same registry) are not flagged as source changes. A dependency's prepare script is not counted: npm never runs it when the package is installed from the registry. Known malware blocks even with fail-on-severity: none: these advisories are often rated only HIGH, and no threshold should let one through.

Scan package.json next to your lockfile. The lockfile lists every installed package, but only package.json has your own install scripts, and with npm and Yarn only it has your overrides / resolutions. A lockfile-only scan misses a PR that adds a root postinstall or repoints an override. pnpm copies overrides into pnpm-lock.yaml, so there it misses only the root scripts. See Quiet on clean PRs.

Why npmscan

  • Private repos are free. GitHub's own Dependency Review action only runs on private repositories that have GitHub Code Security or Advanced Security. npmscan needs neither.
  • It looks beyond CVEs. New install scripts and repointed tarballs are how npm supply-chain attacks usually work, and they have no CVE until someone notices.
  • There's no secret to configure, so it also works on Dependabot PRs and PRs from forks, where repository secrets aren't available.
  • You can read all of it in five minutes. It's a short bash script and a jq template, with nothing to download or build at run time.

Inputs

Input Default Description
file package-lock.json package.json, package-lock.json, npm-shrinkwrap.json, yarn.lock (classic or Berry) or pnpm-lock.yaml, relative to the repo root
files Several files, one per line or comma-separated. Overrides file.
mode block block fails the check on findings that meet the thresholds; warn only reports them
fail-on-severity low Lowest severity that blocks: low, moderate, high, critical, or none. Known malware blocks regardless.
fail-on-install-script true Block on new install scripts (see the table above)
fail-on-source-change true Block on repointed or swapped tarballs and changed overrides
fail-on-error false Fail when the scan itself can't run (npmscan.com unreachable, file over 8 MB). By default this is a warning, so an outage never blocks your merges. If the API hangs, the action gives up within about 5 minutes per file, so give the job at least 5 minutes per scanned file plus some slack (10 for one file, 20 for three). Otherwise a timed-out job fails the check anyway.
comment always When to post the PR comment: always, on-findings (only when something is flagged or the scan fails) or never. With on-findings, an earlier comment is still updated, so fixed findings don't linger. The report always goes to the job summary.
github-token ${{ github.token }} Token used to post the PR comment
fail-on-flagged true Deprecated: false is the same as mode: warn

Outputs

Output Description
flagged-count Findings across all scanned files (empty if nothing was scanned)
blocking-count Findings that meet the thresholds. These fail the check in mode: block.
error-count Files that could not be scanned

Recipes

Try it for a week without blocking anything

      - uses: npmscan/npmscan-action@v1
        with:
          mode: warn

The comment marks what would have blocked. Switch to mode: block when you trust it.

Quiet on clean PRs

on:
  pull_request:
    paths: [package.json, package-lock.json]
# ...
      - uses: npmscan/npmscan-action@v1
        with:
          files: |
            package.json
            package-lock.json
          comment: on-findings

PRs with nothing to report get no comment, just the report in the job summary. A finding or a failed scan still comments, and a later clean push updates that comment. Useful when package.json changes often for reasons that have nothing to do with dependencies.

Only block serious vulnerabilities

      - uses: npmscan/npmscan-action@v1
        with:
          fail-on-severity: high

Monorepos

on:
  pull_request:
    paths: ['**/package.json', '**/package-lock.json']
# ...
      - uses: npmscan/npmscan-action@v1
        with:
          files: |
            package-lock.json
            packages/web/package-lock.json
            packages/api/package-lock.json

All files are reported in one comment, and files the PR doesn't touch are skipped without an API call. Separate npmscan steps each keep their own comment.

Dependabot and Renovate PRs

npmscan works as a security reviewer for dependency-bot PRs. Dependabot PRs run with a read-only token unless the workflow asks for more, so keep the permissions block from the quick start:

permissions:
  contents: read
  pull-requests: write

Renovate PRs come from a branch in your repo and need nothing extra.

PRs from forks

Fork PRs always get a read-only token, so npmscan can't comment. The full report is in the job summary, and findings still show up as annotations and still fail the check.

Use the result in a later step

      - id: npmscan
        uses: npmscan/npmscan-action@v1
        with:
          mode: warn
      - if: fromJSON(steps.npmscan.outputs.blocking-count || '0') > 0
        run: echo "npmscan found ${{ steps.npmscan.outputs.blocking-count }} blocking finding(s)"

Pinning for security-conscious teams

@v1 always points to the latest v1.x.y release, so you get fixes automatically. If your policy requires immutable references, pin to the full commit SHA of a release instead:

      - uses: npmscan/npmscan-action@de14168f65640a4ed1f0e82ad3ad8c80483d360d # v1.3.0

Pick the SHA of the latest release, or print it with git ls-remote https://github.com/NPMscan/npmscan-action refs/tags/v1.3.0. To keep a pinned SHA up to date, let Dependabot do it:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: weekly

What leaves your runner

  • Only the dependency file being scanned, as it is on the base branch and in the PR, is sent over HTTPS to https://npmscan.com/api/analysis/dependency-diff.
  • Never your source code, other files, environment variables, secrets, or the GitHub token.
  • Nothing is stored. The files are used to compute the diff and then discarded.
  • To check vulnerabilities and install scripts, npmscan looks up the package names and versions from that diff on the public npm registry and OSV.dev. For private packages, this means their names reach those services.

Requests share a public rate limit of 30 per minute per IP. Files can be up to 8 MB, and up to 100 changed packages per run are checked in depth. See the privacy policy and security policy.

Contributing and security

The action itself is action.yml plus scripts/. The outreach/ folder holds maintainer notes and tooling (see outreach/README.md). It isn't part of the action, and its Node dev dependencies are never installed or run in your workflow.

MIT License 路 Made by BlockHacks.io 路 Powered by npmscan.com

About

Catch risky npm dependency changes before they merge.

Topics

Resources

Contributing

Security policy

Stars

374 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages