Skip to content
Qi edited this page Aug 3, 2026 · 6 revisions

Welcome to the npm-safe Wiki

@npm-safe is a local-first engine for analyzing npm packages against known supply-chain attack patterns. It fetches package metadata from the public npm registry, runs static analysis rules against the metadata and README content, caches results in a local SQLite database, and exposes a typed API for querying, watching, and refreshing security assessments. The engine is designed to operate as a library rather than a standalone service.

Table of contents

Project status

Phase 1 complete (engine core) + Phase 2 complete.

  • Engine core delivered with 29 source files and zero TypeScript errors.
  • Phase 2 added a full test suite (247 tests, all passing), a CLI binary, proxy support, an optional multi-provider LLM scan provider, and a Neutralinojs desktop GUI.
  • A hardening pass (2026-08-02) fixed 12 issues found by a bug screen, including two critical XSS-to-RCE exposures in the desktop GUI (all fields are now escaped), a watchlist refresh crash, and several CLI correctness problems such as the -j output flag and sub-second TTL precision.
  • CI/CD integration shipped: npm-safe ci command + GitHub Actions workflow.

What it does

  1. Fetches package metadata from the public npm registry (with retry, backoff & proxy support).
  2. Runs static analysis rules against the metadata and README content (pure analysis, no network during scanning).
  3. Caches results in a local SQLite database (WAL mode, TTL-based caching).
  4. Exposes a typed API (NpmSafeEngine) for checking, searching, watching, and refreshing security assessments.

Key features

  • Static analysis — 10 built-in rules detecting install scripts, obfuscation, typosquatting, secret exposure, homograph attacks, and more.
  • CLIcheck, search, watch, refresh, settings, lang, rules, llm, and ci commands, with bilingual (en/zh) output.
  • Desktop GUI — Neutralinojs-based Material You dashboard with check/search/watch/rules/llm/settings tabs, light/dark themes, and persistent check history.
  • LLM scanning — optional semantic scan via OpenAI / Gemini / Anthropic providers with persisted configuration.
  • Rule plugins — load custom rules from ~/.npm-safe/rules/ (ESM files), managed at runtime.
  • CI/CDnpm-safe ci command and a ready-to-use GitHub Actions workflow.
  • Proxy support — for restricted networks, with --proxy flag / persisted setting / env var fallback.

Getting started

Prerequisites

  • Node.js 18 or later (global fetch required)
  • pnpm 9 or later

Setup

pnpm install
pnpm -F @npm-safe/core exec tsc --noEmit

The TypeScript compiler (tsc) is installed as a per-package devDependency under pnpm's isolated store and is not hoisted to the workspace root, so running npx tsc at the top level will fail. Use pnpm -F @npm-safe/core exec tsc --noEmit instead.

Build & link the CLI

pnpm -F @npm-safe/core run build
cd packages/core && npm link

Quick check

npm-safe check lodash

Example output:

Package: lodash
Latest version: 4.18.1
Security level: suspicious
Score: 65/100
Findings: 5
...

CLI reference

npm-safe <package>                 # Shorthand for check
npm-safe check <package>           # Check a package's security posture
npm-safe search <query>            # Search the npm registry
npm-safe watch list                # List watched packages
npm-safe watch add <package>       # Add a package to the watchlist
npm-safe watch remove <package>    # Remove a package from the watchlist
npm-safe refresh [package]         # Refresh one (or all watched) packages
npm-safe settings get <key>        # Read a setting
npm-safe settings set <key> <val>  # Write a setting
npm-safe lang [en|zh]              # Get or set the output language
npm-safe rules list                # List scan rules with effective status
npm-safe rules enable <rule-id>    # Enable a scan rule (persisted)
npm-safe rules disable <rule-id>   # Disable a scan rule (persisted)
npm-safe rules severity <rule-id> <severity>  # Override a rule's severity
npm-safe llm status                # Show LLM provider status
npm-safe llm enable                # Enable LLM scanning
npm-safe llm disable               # Disable LLM scanning
npm-safe llm set-provider <openai|gemini|anthropic>
npm-safe llm set-key <api-key>     # Set the LLM API key
npm-safe llm set-model <model>     # Set the LLM model identifier
npm-safe llm test-connection       # Test the LLM connection
npm-safe ci                        # Scan dependencies, fail the build on severe findings

Global options

  • -d, --db <path> — custom SQLite database path (default ~/.npm-safe/npm-safe.db)
  • -p, --proxy <url> — HTTP proxy for registry requests
  • -j, --json — JSON output
  • -v, --version — print version

Desktop application

A Neutralinojs desktop GUI is provided under packages/desktop/. Run it in development mode:

cd packages/desktop
pnpm run run

Build a release bundle:

pnpm run build

Features:

  • Overview dashboard — average security score on a half-circle gauge, 7-day check histogram, total check count, risk breakdown, recent checks list.
  • Check — enter a package name and view the security level, score, and detailed findings.
  • Search — keyword search against the npm registry; click a result to jump straight to Check.
  • Watch — manage the watchlist and refresh individual or all watched packages.
  • Rules — list all registered rules, toggle each rule, override severity, reload plugin rules.
  • LLM — configure the optional LLM scan (enable switch, provider, API key, model, base URL) with a test-connection button.
  • Settings — read/write arbitrary engine settings (e.g. proxy, lang).
  • Light/Dark themes — two independent Material You palettes, remembered across sessions.
  • Custom window chrome — borderless window with draggable title bar, minimize and close buttons.
  • Persistent check history — stored in ~/.npm-safe/history.json (capped at 1000 entries).

Windows first-run note: if the WebView2 window fails to load with a loopback error, run once in an administrator PowerShell:

CheckNetIsolation.exe LoopbackExempt -a -n="Microsoft.Win32WebViewHost_cw5n1h2txyewy"

Proxy configuration

On restricted networks the registry may only be reachable through a proxy. Proxy resolution order: --proxy flag > persisted proxy setting > HTTPS_PROXY / HTTP_PROXY / ALL_PROXY environment variables. The NO_PROXY variable (exact match, .suffix match, or *) bypasses the proxy.

# Persist a proxy (recommended)
npm-safe settings set proxy http://127.0.0.1:7897

# Or pass it per invocation
npm-safe --proxy http://127.0.0.1:7897 check react

Language switching

npm-safe lang          # Show the current language
npm-safe lang zh       # Switch to Chinese (persisted)
npm-safe lang en       # Switch to English (persisted)

Rules and plugins

Rules can be managed at runtime. Configuration is persisted in ~/.npm-safe/rules.json:

npm-safe rules list                          # Show every rule and its status
npm-safe rules disable install-script        # Disable a rule
npm-safe rules enable install-script         # Re-enable it
npm-safe rules severity typosquatting critical  # Override a rule's severity

Third-party rule plugins can be dropped into ~/.npm-safe/rules/ as ES module files (*.mjs / *.js). Each file may export rule, rules, or default holding one or more rules conforming to the ScanRule interface:

// ~/.npm-safe/rules/my-rule.mjs
export const rule = {
  id: "my-rule",
  name: "My rule",
  description: "Detects something bad",
  severity: "high",
  category: "informational",
  enabled: true,
  match(readme, packageJson) {
    return packageJson?.scripts?.postinstall?.includes("wget")
      ? [{ ruleId: "my-rule", ruleName: "My rule", severity: "high",
           message: "postinstall uses wget", category: "informational" }]
      : [];
  },
};

Plugin files are loaded at engine startup and bad files are skipped. The ScanRule interface and the full engine rule API (registerRule, unregisterRule, listRules, setRuleEnabled, setRuleSeverity) are exported from @npm-safe/core for programmatic use.

LLM scanning

LLM-based semantic scanning is optional and disabled by default. When no API key is configured, static analysis continues normally. Configuration is persisted in ~/.npm-safe/llm.json and can also be supplied via environment variables (OPENAI_API_KEY, GEMINI_API_KEY, or ANTHROPIC_API_KEY).

npm-safe llm status                 # Show the current provider and status
npm-safe llm enable                 # Turn LLM scanning on
npm-safe llm set-provider openai    # Select provider
npm-safe llm set-key $OPENAI_API_KEY
npm-safe llm set-model gpt-4o-mini
npm-safe llm test-connection        # Verify the provider works

CI/CD integration

npm-safe ci scans a project's direct dependencies and fails the build when any dependency reaches a configurable security level:

npm-safe ci --dir ./packages/core          # default fail level: dangerous
npm-safe ci --fail-level suspicious        # stricter gate
npm-safe ci --prod                         # skip devDependencies
npm-safe ci --json                         # machine-readable report
npm-safe ci --rate-limit 50                # registry requests per second

Exit codes: 0 pass, 1 usage/config error, 2 one or more dependencies reached the fail level (or the scan errored). A ready-to-use GitHub Actions workflow lives at .github/workflows/ci.yml — it runs the test suite, type checks, and a dependency security scan on every push/PR.

Architecture

The engine is composed of five layers. Each layer depends only on the layers below it. The index.ts facade composes every dependency and exposes the result as a single NpmSafeEngine class.

                           +-----------------------+
                           |      index.ts          |
                           |  NpmSafeEngine facade  |
                           |  24 public methods     |
                           +-----------+-----------+
                                       |
              +------------------------+------------------------+
              |                        |                        |
     +--------v--------+     +---------v---------+     +--------v--------+
     |   Registry      |     |    Scanner        |     |   Scheduler     |
     |  NpmRegistryClient|   |  StaticAnalyzer   |     | RefreshScheduler|
     |  Validator       |     |  10 rules         |     |  TokenBucket    |
     |  (HTTP fetch)    |     |  (pure analysis)  |     |  (rate-limit)   |
     +--------+---------+     +---------+---------+     +--------+--------+
              |                          |                        |
              |                          |                        |
              +--------------------------+------------------------+
                                         |
                                +--------v--------+
                                |     Store       |
                                | DatabaseManager |
                                |  CacheManager   |
                                |  SQLite (WAL)   |
                                +-----------------+

Layer responsibilities

Layer Module(s) Role
Registry registry/client.ts, registry/validator.ts, registry/types.ts HTTP communication with the npm registry API. Fetches packuments, validates package names and versions, defines registry-facing types.
Scanner scanner/static-rules.ts, scanner/rule-config.ts, scanner/rule-loader.ts, scanner/types.ts Pure static analysis of package metadata and README content. Ten built-in rules plus runtime rule registration, per-rule config overrides, and plugin discovery.
Scheduler scheduler/refresh-scheduler.ts, scheduler/rate-limiter.ts Manages periodic refresh cycles for watched packages. A token bucket (5 tokens/s, 10 burst) limits registry request frequency.
Store store/database.ts, store/cache-manager.ts, store/schema.ts Persistent storage via better-sqlite3 with WAL mode. Handles migrations, TTL-based caching, watchlist persistence, and key-value settings.
Facade index.ts The NpmSafeEngine class composes all four layers. Exposes 24 public methods: checkPackage, searchPackages, watchlist CRUD, refresh operations, settings access, rule management, LLM configuration, and lifecycle (startAutoRefresh, stopAutoRefresh, close).

A sixth auxiliary layer, Translator, provides a pluggable translation interface for converting findings and summaries into different languages.

Key design decisions

Decision Rationale
ESM-only ("type": "module") Aligns with the modern Node.js ecosystem. All imports use .js specifiers as required by native ESM.
Strict TypeScript, no any Every function and interface is fully typed. The project compiles with --strict and zero implicit any.
250-LOC ceiling per module Keeps each file focused and reviewable.
SQLite via better-sqlite3 Zero-configuration embedded database. WAL mode, busy_timeout=5000, synchronous=NORMAL, and foreign keys enabled on open.
Pure static analysis (no network) The scanner inspects only metadata and README text already fetched by the registry client. No external API calls during analysis.
TokenBucket rate limiter (5 tokens/s, 10 burst) Prevents registry throttling.
Cache-first checkPackage with TTL staleness Returns cached results immediately when the TTL has not expired; stale cache triggers a background refresh. Default TTL is 1 hour.
String enums for SecurityLevel / Severity Safe to log, serialize, and use in switch statements without reverse-mapping surprises.
Score: 100 minus severity weights Critical = 25, High = 15, Medium = 8, Low = 3. Starting from 100 ensures an unscored package scores 100 (safe).
Level thresholds >=80 Safe, >=50 Suspicious, >=20 Dangerous, else Unknown. Shared between StaticAnalyzer and CacheManager.

Data locations

Data Path
SQLite database ~/.npm-safe/npm-safe.db
Check history ~/.npm-safe/history.json
Rule configuration ~/.npm-safe/rules.json
LLM configuration ~/.npm-safe/llm.json
Rule plugins ~/.npm-safe/rules/
Extension log (desktop) %TEMP%/npmsafe-extension.log (Windows) / $TMPDIR/npmsafe-extension.log (macOS/Linux)

Documentation

License

Apache License 2.0

Clone this wiki locally