-
Notifications
You must be signed in to change notification settings - Fork 1
Home
@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.
- Project status
- What it does
- Key features
- Getting started
- CLI reference
- Desktop application
- Proxy configuration
- Language switching
- Rules and plugins
- LLM scanning
- CI/CD integration
- Architecture
- Key design decisions
- Data locations
- Documentation
- License
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
-joutput flag and sub-second TTL precision. - CI/CD integration shipped:
npm-safe cicommand + GitHub Actions workflow.
- Fetches package metadata from the public npm registry (with retry, backoff & proxy support).
- Runs static analysis rules against the metadata and README content (pure analysis, no network during scanning).
- Caches results in a local SQLite database (WAL mode, TTL-based caching).
-
Exposes a typed API (
NpmSafeEngine) for checking, searching, watching, and refreshing security assessments.
- Static analysis — 10 built-in rules detecting install scripts, obfuscation, typosquatting, secret exposure, homograph attacks, and more.
-
CLI —
check,search,watch,refresh,settings,lang,rules,llm, andcicommands, 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/CD —
npm-safe cicommand and a ready-to-use GitHub Actions workflow. -
Proxy support — for restricted networks, with
--proxyflag / persisted setting / env var fallback.
pnpm install
pnpm -F @npm-safe/core exec tsc --noEmitThe 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.
pnpm -F @npm-safe/core run build
cd packages/core && npm linknpm-safe check lodashExample output:
Package: lodash
Latest version: 4.18.1
Security level: suspicious
Score: 65/100
Findings: 5
...
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-
-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
A Neutralinojs desktop GUI is provided under packages/desktop/. Run it in development mode:
cd packages/desktop
pnpm run runBuild a release bundle:
pnpm run buildFeatures:
- 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"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 reactnpm-safe lang # Show the current language
npm-safe lang zh # Switch to Chinese (persisted)
npm-safe lang en # Switch to English (persisted)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 severityThird-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-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 worksnpm-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 secondExit 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.
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 | 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.
| 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 | 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) |
- README — project overview (English)
- 中文版 README — 项目说明(中文)
- ARCHITECTURE — layer map, data flows, database schema
- API — public API reference
- SCANNER_RULES — all 10 built-in static analysis rules
Apache License 2.0