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

registry-guard

v0.1.0

Published

Verify npm packages before installation. Detect hallucinated package names, typosquats, suspicious registry metadata, lifecycle scripts, and supply-chain risk signals.

Readme

registry-guard

npm version CI license: MIT node dependencies

GitHub · Issues · Releases · Changelog

Maintained by Nextbridge


registry-guard is an npm package verification CLI and npm registry metadata checker for JavaScript, TypeScript, and Node.js developers. It works as a pre-install package sanity check for dependency names suggested by AI coding assistants, copied from unfamiliar examples, or typed by hand.

It validates npm package names, checks whether packages exist, detects near matches to popular packages, and surfaces typosquatting, slopsquatting, and AI-hallucinated package-name signals from registry metadata. registry-guard reads package age, version history, maintainers, deprecation status, and install-relevant lifecycle scripts, returns low, medium, high, or unknown risk, supports CLI and programmatic API usage, and does not execute inspected package scripts.


Contents

The Problem

AI coding assistants can recommend plausible npm packages that do not exist. Attackers may publish package names that resemble popular packages, and developers can also mistype legitimate dependency names during normal work. Installing an unknown package without checking it first creates software supply-chain risk.

AI suggestion:
  npm install expres

registry-guard:
  -> package does not exist
  -> likely meant "express"

registry-guard gives you a quick registry-backed check before you install.

Quick Start

npx registry-guard expres

Install the CLI globally:

npm install -g registry-guard
registry-guard expres

Install for package or API usage:

npm install registry-guard

Features

  • npm registry existence checks - confirms whether a package name exists on the configured npm registry.
  • Scoped package support - accepts scoped names such as @types/node and encodes them correctly for registry requests.
  • Multiple package checks - checks one or more names in a single CLI invocation or with checkPackages().
  • Strict package-name validation - rejects malformed, uppercase, Unicode, whitespace, control-character, and incorrectly scoped names before making registry requests.
  • Typosquat and near-match detection - compares package names with a built-in popular-package list using Levenshtein edit distance.
  • Hallucinated package detection - flags nonexistent names and, when available, reports the likely popular package that was intended.
  • Lifecycle script visibility - reports preinstall, install, postinstall, and prepare scripts on the latest version without installing the package.
  • Package-age signal - marks packages created less than 30 days ago as medium risk.
  • Version-history signal - marks packages with only one published version as medium risk.
  • Maintainer metadata signal - distinguishes unavailable maintainer data from an explicitly empty maintainer list.
  • Deprecated package signal - reports a latest-version deprecation message as medium risk.
  • JSON output - prints structured result arrays with --json.
  • Configurable registry - uses https://registry.npmjs.org by default and supports a caller-selected registry URL.
  • Configurable timeout - uses a 5000ms registry timeout by default.
  • Configurable concurrency - checks up to 5 packages concurrently by default.
  • Terminal-safe CLI output - strips ANSI escapes, control characters, and line breaks from displayed user and registry text.
  • Structured exit codes - returns separate codes for high risk, usage errors, and unknown registry analysis.
  • TypeScript declarations - bundles .d.ts declarations for the public API.
  • CLI and programmatic API - works from registry-guard or from ESM imports.
  • No inspected lifecycle-script execution - reads package metadata only; it does not install inspected packages.

Why & Who It's For

Use registry-guard when you want a small local check before trusting a dependency name. It is especially useful for developers reviewing AI-generated dependency suggestions, security-conscious JavaScript and TypeScript teams, CI pipelines validating package names, developer tools and internal platforms, and users checking unfamiliar or mistyped npm packages.

| Approach | Confirms existence | Detects near matches | Shows registry risk signals | Runs locally | | --------------------- | ------------------ | -------------------- | --------------------------- | -------------- | | npm search | Partial | No | No | Yes | | Manual registry check | Yes | No | Partial | Browser or CLI | | registry-guard | Yes | Yes | Yes | Yes |

registry-guard is not a replacement for code review, dependency audit tooling, or organizational package policy. It is a focused pre-install sanity check for package names and registry metadata.

Installation

Requires Node.js >= 18. registry-guard is ESM-only, matching its package.json type: "module" and import-only exports map.

Run without installing:

npx registry-guard expres

Install globally for a persistent CLI:

npm install -g registry-guard
registry-guard expres

Install locally for the programmatic API:

npm install registry-guard

Direct require("registry-guard") is unsupported because the package only exports an ESM import entry. CommonJS consumers can use dynamic import():

(async () => {
  const { checkPackage } = await import("registry-guard");
  const result = await checkPackage("express");
  console.log(result.risk);
})();

For local development from this repository:

npm install
node bin/cli.js expres

Usage

Check one package:

registry-guard expres

Check multiple packages:

registry-guard expres lodahs @types/node

Check a scoped package:

registry-guard @types/node

Print JSON:

registry-guard expres --json

Use a custom registry:

registry-guard @types/node --registry https://registry.npmjs.org/

Set the registry timeout:

registry-guard express --timeout 10000

Set package-check concurrency:

registry-guard express react lodash --concurrency 2

Use options together:

registry-guard expres @types/node --json --registry https://registry.npmjs.org/ --timeout 10000 --concurrency 2

Use exit codes in CI:

registry-guard express react @types/node
case "$?" in
  0) echo "registry-guard passed" ;;
  1) echo "high-risk package signal found"; exit 1 ;;
  2) echo "registry-guard usage error"; exit 2 ;;
  3) echo "registry analysis was unknown"; exit 3 ;;
esac

For local development from a clone, use node bin/cli.js:

node bin/cli.js express --json

Architecture

Package name(s)
      |
      v
Name validation
      |
      v
npm registry request
      |
      v
Metadata validation
      |
      v
Risk signals + near-match detection
      |
      +---- CLI output + exit code
      |
      +---- Programmatic result

CLI Options

| Option | Default | Value format | Output | Description | | ------------------------ | ---------------------------- | --------------------------- | ----------------------- | ------------------------------------------------------- | | -h, --help | None | None | stdout | Show help and exit 0. | | -v, --version | None | None | stdout | Print the package version and exit 0. | | --json | false | None | stdout | Print an array of structured results as formatted JSON. | | --registry <url> | https://registry.npmjs.org | http:// or https:// URL | stderr on invalid input | Use a custom npm registry URL. | | --timeout <ms> | 5000 | Positive integer | stderr on invalid input | Set registry request timeout in milliseconds. | | --concurrency <number> | 5 | Positive integer | stderr on invalid input | Set the maximum number of concurrent package checks. |

Unknown options, missing option values, invalid option values, missing package names, and invalid package names exit 2. Usage errors are written to stderr with help text. --registry in the CLI must be http:// or https://; --timeout and --concurrency must be positive integers such as 1000 or 2.

Example Output

Likely typo or nonexistent package:

$ registry-guard expres

 HIGH RISK   expres
  -> This package does not exist on the npm registry.
  -> It looks similar to the popular package "express" - this may be a hallucinated or typosquatted name.

Illustrative existing low-risk package:

$ registry-guard established-package

 LOW RISK   established-package
  latest: 4.2.0  *  versions: 42  *  maintainers: 3
  -> No red flags found: package exists, has version history, and no install-time scripts detected.

Illustrative medium-risk metadata result:

$ registry-guard new-helper-package

 MEDIUM RISK   new-helper-package
  latest: 1.0.0  *  versions: 1  *  maintainers: 0
  -> Published very recently (4 days ago) - new packages carry more supply-chain risk.
  -> Only one published version exists - limited track record.
  -> The registry entry explicitly lists no maintainers.

JSON result structure:

[
  {
    "name": "safe-package",
    "exists": true,
    "risk": "medium",
    "signals": [
      {
        "code": "missing_maintainers",
        "severity": "medium",
        "message": "The registry entry explicitly lists no maintainers."
      }
    ],
    "metadata": {
      "name": "safe-package",
      "latestVersion": "1.0.0",
      "createdAt": "2020-01-01T00:00:00.000Z",
      "modifiedAt": "2020-01-02T00:00:00.000Z",
      "versionCount": 2,
      "hasInstallScript": false,
      "lifecycleScripts": {},
      "maintainerStatus": "empty",
      "maintainerCount": 0,
      "description": "",
      "deprecated": null
    }
  }
]

The CLI sanitizes displayed text. Programmatic results return structured values directly.

Exit Codes

| Code | Meaning | Streams | | ---- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | | 0 | All checked packages are low or medium risk. | Results on stdout. | | 1 | At least one checked package is high risk and no checked package is unknown. | Results on stdout. | | 2 | Usage error: missing package name, unknown option, missing option value, invalid option value, or invalid package name. | Error and usage text on stderr. | | 3 | At least one checked package is unknown because registry analysis could not be completed reliably. | Results on stdout unless the error is a usage error. |

For multiple packages, the CLI uses the highest numeric result code. That means an unknown result exits 3 even if another checked package is high risk.

Risk Signals

Risk signals are heuristics. low does not prove safety, and unknown means reliable registry analysis could not be completed.

| Code | Severity | Meaning | | --------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | registry_error | unknown | The registry request failed, timed out, returned a non-OK status other than 404, returned invalid JSON, or returned malformed metadata. | | not_found | high | The registry returned 404; the package does not exist at that registry. | | near_popular_package | high | A nonexistent package name is close to a built-in popular package name. | | near_popular_package | medium | An existing package name is close to a built-in popular package name. | | no_similar_package | high | A package is missing and no similar built-in popular package was found. | | newly_published | medium | The package creation date is less than 30 days old. Future creation dates are ignored for this signal. | | low_version_history | medium | The package has one published version. | | install_lifecycle_scripts | medium | The latest version declares preinstall, install, postinstall, or prepare. | | missing_maintainers | medium | The registry explicitly lists no maintainers. | | deprecated_package | medium | The latest version has a registry deprecation message. | | no_risks_detected | low | No checked risk signals were detected. |

Programmatic API

import {
  checkPackage,
  checkPackages,
  findNearMatches,
  levenshtein,
  validatePackageName,
} from "registry-guard";

const result = await checkPackage("expres");
console.log(result.risk, result.signals);

checkPackage(name, options?) -> Promise<CheckPackageResult>

Runs the full registry metadata and near-match check for one package name.

Options:

| Option | Type | Default | Description | | ----------------- | ------------------- | ---------------------------- | ---------------------------------------------------------------------------------- | | timeoutMs | number | 5000 | Registry request timeout in milliseconds. Must be a positive finite number. | | registryUrl | string | https://registry.npmjs.org | Registry base URL. The API requires a non-empty valid URL string. | | fetchImpl | FetchLike | global fetch | Custom fetch implementation for tests, offline fixtures, or controlled networking. | | popularPackages | readonly string[] | built-in list | Package names used for near-match detection. Each name is validated. |

Returns { name, exists, risk, signals, metadata }. Invalid package names and invalid options throw TypeError before a registry request. Registry request failures, malformed registry responses, and timeouts return an unknown result with a registry_error signal.

checkPackages(names, options?) -> Promise<CheckPackageResult[]>

Checks multiple package names and preserves input order in the returned result array. names must be an array, every name is validated before requests begin, and concurrency defaults to 5. Invalid concurrency values throw TypeError.

checkPackages() accepts the same options as checkPackage() plus:

| Option | Type | Default | Description | | ------------- | -------- | ------- | ------------------------------------------------------------ | | concurrency | number | 5 | Maximum concurrent registry checks. Must be an integer >= 1. |

validatePackageName(name) -> string

Validates an npm package name and returns it unchanged. It accepts valid scoped and unscoped lowercase package names such as express, my-package.js, @types/node, and @scope/pkg_name. It throws TypeError for non-strings, empty names, names longer than 214 characters, whitespace, control characters, invalid scoped forms, slashes in unscoped names, uppercase characters, Unicode characters, and unsupported leading characters such as . or _.

levenshtein(a, b) -> number

Returns the Levenshtein edit distance between two strings. It is exported for callers that want the same primitive used by registry-guard near-match detection.

findNearMatches(name, popularPackages?, maxResults?) -> NearMatch[]

Finds popular-package names that are close to name, excluding exact matches. It defaults to the built-in popular-package list and returns up to 3 matches sorted by edit distance unless maxResults is provided. Package names with length 5 or less use an edit-distance threshold of 1; longer names use a threshold of 2.

TypeScript Support

Type declarations are bundled and exposed through the package types field and exports map. No separate @types package is required.

import { checkPackage, type CheckPackageResult } from "registry-guard";

const result: CheckPackageResult = await checkPackage("express");

For TypeScript projects, use ESM-compatible module resolution such as NodeNext when your project configuration requires Node-style package export resolution.

Public type exports:

| Export | Kind | Notes | | ------------------------ | --------- | ------------------------------------------------------------- | | RiskLevel | type | "low", "medium", "high", or "unknown". | | MaintainerStatus | type | "available", "empty", or "unavailable". | | LifecycleScriptName | type | "preinstall", "install", "postinstall", or "prepare". | | PackageMetadataSummary | interface | Normalized registry fields used by risk checks. | | FetchResponseLike | interface | Minimal fetch response shape accepted by fetchImpl. | | FetchInitLike | interface | Minimal fetch init shape passed to fetchImpl. | | FetchLike | type | Custom fetch implementation signature. | | CheckPackageOptions | interface | Options for checkPackage(). | | CheckPackagesOptions | interface | Options for checkPackages(), including concurrency. | | RiskSignal | type | Discriminated union of public risk signal shapes. | | CheckPackageResult | interface | Result returned by package checks. | | NearMatch | interface | { popular, distance } near-match entry. |

How It Works

  1. validatePackageName() rejects malformed package names before any registry request.
  2. fetchPackageMetadata() builds <registryUrl>/<encoded-name>, trimming trailing slashes from the registry URL and encoding scoped package slashes.
  3. An AbortController enforces the registry timeout and reports timed-out requests as registry errors.
  4. Registry responses are handled deliberately: 404 means not found, non-OK statuses are errors, invalid JSON is an error, and metadata must contain a matching name, non-empty versions, and a valid dist-tags.latest.
  5. summarizeMetadata() normalizes the latest version, created and modified dates, version count, lifecycle scripts, maintainer status, description, and deprecation text.
  6. findNearMatches() compares the package name with the built-in or caller-provided popular-package list.
  7. checkPackage() calculates risk signals for missing packages, near matches, newly published packages, low version history, lifecycle scripts, missing maintainers, and deprecation.
  8. The CLI sanitizes names and registry text before rendering terminal output.
  9. The CLI selects exit code 0, 1, 2, or 3 from the final result set.

Security

registry-guard reads npm registry metadata. It does not install inspected packages, execute inspected lifecycle scripts, inspect tarballs, run package code, verify provenance, or query a vulnerability database.

Invalid package names are rejected before registry requests. CLI output is sanitized to remove ANSI escapes, terminal control characters, carriage returns, and line breaks from user-controlled and registry-controlled strings before display.

Custom registry URLs can contact caller-selected endpoints. CLI registry URLs are limited to http:// and https://, while the programmatic API accepts any syntactically valid URL string. If you expose registryUrl to untrusted users in a service environment, validate or restrict it before calling registry-guard.

The package does not contain telemetry code. Dependency audit results can change over time and should not be treated as a permanent security guarantee.

Report security issues through GitHub Issues.

Limitations

IMPORTANT: registry-guard detects suspicious indicators. It does not prove that a package is safe.

  • Metadata analysis is heuristic and limited to registry fields used by the implementation.
  • The popular-package list is static and non-exhaustive; it is not refreshed from live download counts.
  • Levenshtein distance catches simple near-misses, but it can miss word reordering, package-name look-alikes beyond the threshold, and many social-engineering patterns.
  • Unicode and confusable-character handling is intentionally strict for accepted names, but registry-guard is not a full confusable-name analysis engine.
  • No tarball inspection is performed.
  • No malware analysis is performed.
  • No vulnerability database lookup is performed.
  • No provenance, repository ownership, maintainer identity, or publisher ownership verification is performed.
  • No download-count, maintainer-activity, or release-cadence intelligence is used beyond creation date and version count.
  • Network access is required for live registry checks.
  • Custom registries are trusted as the source of metadata for that invocation.
  • A low result means no implemented risk signal fired; it is not a safety guarantee.

Troubleshooting

Exit code 2 appears. The CLI found a usage error: missing package name, unknown option, missing option value, invalid option value, or invalid package name. The error and usage text are printed to stderr.

Exit code 3 appears. At least one result was unknown. This can happen when the registry is unreachable, a request times out, a non-404 registry response is not OK, JSON parsing fails, or the registry response is malformed.

A package exists but the result is unknown. The registry returned data that registry-guard could not reliably validate, such as a missing versions object or a dist-tags.latest value that does not point to a published version.

A custom registry URL is rejected. The CLI requires --registry values to be valid http:// or https:// URLs. The programmatic API requires a non-empty valid URL string.

A timeout value is rejected. The CLI accepts positive integers only, such as 1000. The API accepts positive finite numbers through timeoutMs.

require("registry-guard") fails. registry-guard is ESM-only and exposes an import condition. Use import { checkPackage } from "registry-guard" or dynamic import() from CommonJS.

A scoped package name fails validation. Scoped names must use the exact @scope/name form. Unscoped names must not contain /.

Maintainers show as unavailable instead of 0. Unavailable means the registry response did not include usable maintainer data. An explicit empty maintainer list is reported as 0 and adds the missing_maintainers signal.

A near-match result is unexpected. Near-match detection compares against the static built-in popular-package list. Exact matches are ignored, short popular names use a stricter threshold, and unrelated names may return no matches.

FAQ

Does low risk mean safe? No. It means none of the implemented metadata heuristics found a red flag.

Does registry-guard install or execute packages? No. It reads registry metadata and does not execute inspected package lifecycle scripts.

Can it detect malware? No. It is not a malware scanner and does not inspect package tarballs or execute code.

Can it inspect private or custom registries? Yes, when you provide --registry <url> in the CLI or registryUrl in the API. Treat custom registry metadata according to how much you trust that registry.

Can I use it programmatically? Yes. Import checkPackage(), checkPackages(), validatePackageName(), levenshtein(), or findNearMatches() from registry-guard.

Does it work with scoped packages? Yes. Names such as @types/node are supported.

Can it check multiple packages? Yes. Pass multiple names to the CLI or use checkPackages().

Does it support CommonJS? Direct require("registry-guard") is unsupported. CommonJS callers can use dynamic import().

How is typosquatting detected? registry-guard compares names against a static popular-package list using Levenshtein edit distance. It reports close non-exact matches.

Can I provide my own popular-package list? Yes. Pass popularPackages to checkPackage() or checkPackages().

Changelog

See CHANGELOG.md for the complete release history.

Contributing

Issues and pull requests are welcome. Before opening a pull request, run the local verification commands used by this package:

npm test
npm run test:integration

npm run test:integration uses the live npm registry, so it requires network access.

License

MIT © Nextbridge

Built and maintained by Nextbridge — if registry-guard helped you and you find it useful, a ⭐ is appreciated.