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

ossrisk

v0.9.0

Published

Scan dependencies for supply-chain risk: EOL versions, CVEs, abandonment, typosquatting, license compliance, maintainer takeover patterns, and OPA policy gating

Readme

ossrisk

Scan your dependencies for long-term viability and supply-chain risk: EOL versions, known CVEs, abandonment signals, typosquatting, license compliance, and maintainer takeover patterns.

Supports package.json / package-lock.json (npm) and requirements.txt / Pipfile.lock (PyPI). When a lockfile is present, the full resolved tree (direct + transitive) is scanned and each flagged transitive shows the direct dep it came in through.

CI npm License: MIT


Install

npm install -g ossrisk

Or run without installing:

npx ossrisk .

CLI usage

ossrisk [path] [options]

| Option | Default | Description | |---|---|---| | [path] | . | Path to project directory to scan | | -f, --format <fmt> | table | Output format: table, json, markdown, cyclonedx, spdx | | --fail-on <level> | high | Exit 1 if any dep reaches this risk level (none|low|medium|high|critical) | | --policy <path> | | Evaluate results against OPA Rego policies — file or directory (requires the opa CLI) | | -c, --concurrency <n> | 8 | Concurrent API requests per batch | | --no-eol | | Skip EOL checks | | --no-cve | | Skip CVE checks | | --no-activity | | Skip abandonment/staleness checks | | --no-outdated | | Skip latest-version checks | | --no-typosquat | | Skip typosquatting checks | | --no-license | | Skip license compliance checks | | --no-maintainer | | Skip maintainer/publisher checks | | --no-install-script | | Skip install-script (preinstall/postinstall) checks | | --direct-only | | Scan only direct dependencies, skip transitives | | --list-checkers | | List the available risk checkers and exit |

Examples

# Scan the current directory
ossrisk

# Scan a specific project
ossrisk /path/to/project

# Output as JSON
ossrisk . --format json

# Fail on medium risk or above
ossrisk . --fail-on medium

# Skip CVE checks, output markdown
ossrisk . --no-cve --format markdown

# Gate entirely on an OPA Rego policy
ossrisk . --fail-on none --policy ./policy/supply-chain.rego

# Export a CycloneDX SBOM (with the CVEs ossrisk found embedded)
ossrisk . --fail-on none --format cyclonedx > sbom.cdx.json

# Export an SPDX SBOM
ossrisk . --fail-on none --format spdx > sbom.spdx.json

Risk levels

| Level | Triggers | |---|---| | critical | CVE with CVSS ≥ 9.0 | | high | CVE with CVSS 7.0–8.9, EOL version, or suspected typosquat of a popular package | | medium | CVE with CVSS 4.0–6.9, no release in 24+ months (abandoned), strong-copyleft license (GPL/AGPL/SSPL/…), or new-publisher pattern on a >180-day-old package | | low | CVE with CVSS < 4.0, no release in 12–24 months (stale), newer version available, weak-copyleft license (LGPL/MPL/EPL/…), unknown license, sole maintainer, or install lifecycle hooks (preinstall/install/postinstall) | | none | No issues found |

Maintainer / publisher signals

Two patterns surfaced from npm packument metadata:

  • new-publisher (medium) — the latest release is published by an account that did not publish any of the first three releases, and the package is older than 180 days. This is the event-stream-style takeover pattern: long-running package, sudden new face on the most recent publish. False positives are possible (legitimate maintainer handoffs); treat as "review before pinning."
  • sole-maintainer (low) — only one maintainer is registered on the package. Informational bus-factor signal, not a vulnerability.

Both checks are npm-only for now; PyPI's JSON API doesn't expose comparable per-version uploader history. Use --no-maintainer to skip these checks.

Install scripts

npm packages can declare lifecycle hooks (preinstall, install, postinstall) that run automatically on npm install. These are a known supply-chain attack vector — several high-profile compromises (e.g. event-stream, node-ipc) used postinstall scripts to exfiltrate data or deliver payloads.

ossrisk fetches the version-specific manifest from the npm registry and flags any package that declares one or more of these hooks as low risk. The signal is informational: many legitimate packages (native add-ons, build tools) use node-gyp rebuild as an install script. The intent is to surface the signal so you can consciously decide whether a package's install-time code execution is expected. Use --no-install-script to suppress this check.

Licenses

ossrisk reads each package's declared license from the npm or PyPI registry, normalizes common variants to SPDX identifiers, and categorizes them:

  • permissive (MIT, Apache-2.0, BSD, ISC, …) — not flagged
  • weak-copyleft (LGPL, MPL, EPL, CDDL, …) — flagged as low
  • strong-copyleft (GPL, AGPL, SSPL, OSL, EUPL) — flagged as medium
  • unknown (missing, UNKNOWN, or unrecognizable text) — flagged as low

The check exists to surface licenses that need legal review before commercial use; it is not a judgement that any of these licenses are bad. Use --no-license if your project doesn't need this signal.

Transitive dependencies

ossrisk scans the full resolved dependency tree when a lockfile is present:

  • npm: package-lock.json (lockfileVersion 2+) — every package listed is scanned; dev-only deps are excluded.
  • PyPI: Pipfile.lockdefault group is scanned; if the lockfile preserves _meta.pipfile.packages, entries are tagged as direct or transitive accordingly.

For flagged transitives, the report shows the direct dependency that pulls them in (via express), so you know which top-level package to update or replace. Use --direct-only to skip transitives entirely.

Without a lockfile (e.g. only package.json or only requirements.txt), ossrisk falls back to direct-only scanning.

Typosquatting

ossrisk compares each dependency name against a curated list of popular npm and PyPI packages, flagging anything within edit distance 2 or matching common homoglyph substitutions (e.g. rnm, 1l). A scoped package like @vendor/lodash is compared by its basename. The check is purely local — no API calls.


SBOM export

ossrisk can emit a Software Bill of Materials over the same resolved dependency tree it scans, in either of the two industry-standard formats:

ossrisk . --fail-on none --format cyclonedx > sbom.cdx.json   # CycloneDX 1.5
ossrisk . --fail-on none --format spdx      > sbom.spdx.json   # SPDX 2.3

Both documents are written to stdout as JSON. Every dependency (direct and transitive) becomes a component/package identified by a package URL — e.g. pkg:npm/[email protected], pkg:pypi/[email protected].

  • CycloneDX additionally folds ossrisk's findings back into the document: any CVEs are written to the standard vulnerabilities section (with OSV as the source and severity ratings), declared non-permissive licenses appear under licenses, and each component carries ossrisk:riskLevel / ossrisk:direct / ossrisk:via properties. So the SBOM doubles as a vulnerability report.
  • SPDX lists each package with its purl external reference and declared license (falling back to NOASSERTION when ossrisk didn't resolve one), plus a DESCRIBES relationship from the document to every package.

Tip: pair --format cyclonedx/spdx with --fail-on none when you only want the artifact — otherwise a risky dependency still makes the process exit 1 (useful if you want CI to both produce an SBOM and gate on risk).


Checkers

Each risk dimension is an independent checker registered against a common interface. ossrisk --list-checkers prints the active set:

cve             Known CVEs / advisories via the OSV batch API
eol             End-of-life runtime/framework versions
activity        Abandonment / staleness from last-release date
outdated        A newer version is available
license         Copyleft / unknown license compliance
maintainer      New-publisher / sole-maintainer takeover patterns
install-script  preinstall / install / postinstall lifecycle hooks
typosquat       Typosquat of a popular package (local, no API)

Every checker has a matching --no-<name> flag to skip it. A checker either runs once over all dependencies (a batched batch pre-pass, like cve) or per-dependency (a check), returning risk signals that are merged into the report. The registry lives in src/checkers/index.ts — adding a dimension is a matter of implementing the Checker interface and appending it there.


GitHub Actions

Add ossrisk to your CI pipeline to automatically scan dependencies on every pull request.

- name: Scan dependencies
  uses: depkeep/ossrisk@v1
  with:
    fail-on: high
    github-token: ${{ secrets.GITHUB_TOKEN }}

When github-token is provided and the workflow runs on a pull request, ossrisk posts a markdown report as a PR comment.

Action inputs

| Input | Default | Description | |---|---|---| | path | . | Path to the project directory | | fail-on | high | Exit 1 if any dep reaches this level or above | | no-eol | false | Skip EOL checks | | no-cve | false | Skip CVE checks | | no-activity | false | Skip abandonment/staleness checks | | no-outdated | false | Skip latest-version checks | | no-typosquat | false | Skip typosquatting checks | | no-license | false | Skip license compliance checks | | no-maintainer | false | Skip maintainer/publisher checks | | no-install-script | false | Skip install-script (preinstall/postinstall) checks | | direct-only | false | Scan only direct dependencies, skip transitives | | github-token | | GitHub token for posting a PR comment |

Action outputs

| Output | Description | |---|---| | risk-level | Highest risk level found across all dependencies |


Policy as code (OPA)

--fail-on is a single threshold. For richer rules — "no critical CVEs anywhere, no strong-copyleft in direct deps, block install scripts from brand-new publishers" — ossrisk integrates with Open Policy Agent:

ossrisk . --fail-on none --policy ./policy/supply-chain.rego

Requires the opa CLI on your PATH. --policy accepts a single .rego file or a directory of policies.

Policies receive the scan result as input — the same JSON that ossrisk --format json prints, which is a stable contract. They live in package ossrisk and add messages to a deny set; any message is reported as a violation and makes ossrisk exit 1:

package ossrisk

import rego.v1

# Block strong copyleft, but only for direct dependencies.
deny contains msg if {
	some dep in input.results
	dep.isDirect
	some sig in dep.signals
	sig.type == "license"
	sig.category == "strong-copyleft"
	msg := sprintf("%s@%s is %s (strong copyleft)", [dep.name, dep.version, sig.license])
}

See examples/policies/supply-chain.rego for a fuller policy, including a cross-signal rule for the event-stream takeover pattern (install scripts + new publisher).

--fail-on and --policy are independent — either can fail the scan. Use --fail-on none to delegate gating entirely to your policy. Violations print to stderr, so --format json output stays pipeable.


Programmatic API

import { scan } from 'ossrisk';

const result = await scan({
  path: '/path/to/project',
  format: 'json',
  failOn: 'high',
  concurrency: 8,
  noEol: false,
  noCve: false,
  noActivity: false,
  noOutdated: false,
  noTyposquat: false,
  noLicense: false,
  noMaintainer: false,
  noInstallScript: false,
  directOnly: false,
});

console.log(result.summary);
// { total: 42, critical: 0, high: 1, medium: 3, low: 5, clean: 33 }

Data sources

  • CVEsOSV.dev batch API
  • EOL datesendoflife.date API
  • Activity — npm registry / PyPI JSON API
  • Latest versions — npm registry / PyPI JSON API
  • Typosquatting — local curated list of popular npm & PyPI packages (no API calls)
  • Licenseslicense field from npm registry; info.classifiers and info.license from PyPI
  • Maintainer signalsmaintainers, versions[v]._npmUser, and time from the npm registry (npm only)
  • Install scriptsscripts field from the version-specific npm registry manifest (npm only)

All checks are read-only and require no API keys.


Contributing

git clone https://github.com/depkeep/ossrisk.git
cd ossrisk
npm install
npm test          # run tests
npm run dev .     # run CLI from source

Before submitting a PR, run npm run build and commit the updated dist/ so the GitHub Action stays functional.


License

MIT © DepKeep