X-ray vision for npm packages.
A security scanner that audits source code, detects obfuscation, and flags supply chain risks -- all before you install. Downloads the tarball, runs 8 scanners in parallel, checks GitHub health, diffs source against the published package, and gives you a 0-100 risk score. No account required. Runs entirely locally.
npx npx-ray chalk # Scan any package
npx npx-ray chalk@5.0.0 # Scan specific version
npx npx-ray --json chalk # JSON output for CI/CD
npx npx-ray --mcp # Scan your MCP servers
npx npx-ray --verbose express # Detailed findingsOr install globally:
npm install -g npx-ray
npx-ray lodashWe ran npx-ray against its own source code. The result: 45/100 (F) -- DANGER. Here's why, and why that's expected.
npx-ray's scanners detect dangerous patterns like eval(), child_process, exec(), and -----BEGIN PRIVATE KEY----- by searching for those strings in source code. But npx-ray's own detection rules contain those exact strings as regex patterns. The scanner flags its own signatures:
| Scanner | Self-Flag | Why |
|---|---|---|
| Static Analysis | 10 critical, 20 warnings | static.js contains detection patterns for eval(), exec(), spawn(), etc. -- the scanner finds its own rules |
| Secrets | 1 critical | secrets.js contains the regex -----BEGIN.*PRIVATE KEY----- as a detection pattern -- flagged as a private key |
| Obfuscation | 2 warnings | ioc.js and obfuscation.js contain hex escape sequences used for pattern matching |
| IOC | 1 IP | ioc.js contains the example IP 1.2.3.4 in its test ignore list |
This is the "who watches the watchmen" problem -- any security tool that scans for dangerous patterns will inevitably contain those patterns in its own detection rules. This does not affect scanning of other packages. We publish this result for full transparency.
Full source code: github.com/txdadlab/npx-ray
npx-ray v1.0.0 — X-ray vision for npm packages
───────────────────────────────────────────────────────
Package: chalk@5.4.1
Publisher: sindresorhus
Published: 2024-11-21
License: MIT
Files: 7
Size: 11.2 KB
───────────────────────────────────────────────────────
Risk Score: 90/100 (A)
───────────────────────────────────────────────────────
Scan Results
✅ Static: No dangerous patterns detected
✅ Obfuscation: No obfuscation detected
✅ Hooks: No lifecycle hooks found
✅ Secrets: No embedded secrets detected
✅ Binaries: No binary files found
✅ Dependencies: 0 direct, 0 optional dependencies
✅ Typosquatting: "chalk" is a known popular package
GitHub
✅ Repository: chalk/chalk
Stars: 22000 | Forks: 850 | Open Issues: 12
Created: 2013-07-18 | Last Push: 2024-11-21
Source Diff
✅ Source matches published package
───────────────────────────────────────────────────────
Verdict: CLEAN — No issues detected
───────────────────────────────────────────────────────
Scan completed in 3.2s
| Scanner | What it detects | Severity |
|---|---|---|
| Static Analysis | eval(), child_process, exec(), spawn(), fetch(), dynamic require(), filesystem writes, process.env access |
Critical / Warning / Info |
| Obfuscation | Shannon entropy anomalies, hex-encoded strings, base64 blobs (>500 chars), string array rotation (>50 elements), suspiciously long lines | Critical / Warning / Info |
| Lifecycle Hooks | preinstall, postinstall, and other install-time scripts; shell commands in hooks (curl, wget, bash, node -e) |
Critical / Warning |
| Secrets | AWS keys, GitHub/npm tokens, private keys, credentials in URLs, generic API keys and tokens | Critical / Warning |
| Binaries | .node, .so, .dll, .dylib, .exe, .bin, .wasm files that cannot be source-reviewed |
Warning |
| Dependencies | Dependency bloat (>20 warning, >50 critical), wildcard/unpinned versions (*, latest), git URL dependencies |
Critical / Warning |
| Typosquatting | Package names within 1-2 edits of popular packages (Levenshtein distance) | Critical / Warning |
| GitHub Health | Stars, forks, archive status, repo age, publisher-vs-owner mismatch | Scoring adjustment |
| Source Diff | Files in npm but not in GitHub repo, content hash mismatches between published and source | Scoring adjustment |
| IOC Extraction | URLs and IP addresses extracted and defanged for safe review; decodes hex escapes, unicode escapes, String.fromCharCode(), and base64 |
Warning (decoded) / Info (plaintext) |
| MCP Servers | Unpinned MCP server versions in editor configs, tool description injection risks | Via --mcp flag |
npx-ray is a static analysis tool. It does not execute any code from scanned packages. This means there are classes of threats it cannot fully detect:
- Cross-file obfuscation -- A payload split across multiple files (e.g., encrypted blob in
data.jswith the decryption key inutils.js) cannot be reassembled by static analysis. The obfuscation scanner will flag the suspicious patterns, but the IOC scanner cannot extract the hidden URL. - Key-based encoding -- XOR ciphers, custom lookup tables, or any encoding that requires a runtime key to decode. npx-ray can decode self-contained encodings (hex, unicode, base64, charCode) but not schemes that depend on external keys or functions.
- Runtime string construction -- Patterns like
arr[3] + arr[7] + arr[1]where array elements are defined elsewhere, or template literals assembled from variables across scopes. - Multi-stage deobfuscation -- Pipelines like base64 decode -> XOR -> URL, where the output of one decoding step feeds into another that requires context.
- Novel or custom obfuscation -- Obfuscation techniques not covered by the built-in pattern library. The entropy scanner provides a general safety net (obfuscated code tends to have high Shannon entropy), but purpose-built obfuscators can evade specific signature checks.
- Native binary analysis --
.node,.so,.dll, and.wasmfiles are flagged as unreviable but their contents are not analyzed.
What this means in practice: npx-ray catches the majority of real-world supply chain attacks, which overwhelmingly rely on plaintext or simple single-layer obfuscation. Sophisticated multi-file encrypted payloads exist but are rare in npm malware. When npx-ray encounters obfuscation it can't decode, the obfuscation scanner still flags the suspicious patterns -- you just won't see the decoded IOC.
For defense-in-depth, combine npx-ray with runtime monitoring, sandboxed installs, and lockfile auditing.
Every package receives a score from 0 (dangerous) to 100 (clean), computed by summing weighted category scores. Each category starts at its maximum points and deducts based on the severity of findings.
| Category | Max Points | Critical Deduction | Warning Deduction | Info Deduction |
|---|---|---|---|---|
| Static Analysis | 25 | -15 | -5 | 0 |
| Obfuscation | 15 | -10 | -10 | -3 |
| Lifecycle Hooks | 10 | -10 | -5 | 0 |
| Dependencies | 10 | -10 | -5 | 0 |
| GitHub Health | 15 | -- | -- | -- |
| Source Diff | 10 | -- | -- | -- |
| Secrets | 5 | -5 | -5 | 0 |
| Binaries | 5 | -3 | -3 | -1 |
| Typosquatting | 5 | -5 | -5 | 0 |
| Total | 100 |
GitHub Health deductions: archived repo (-10), zero stars (-5), repo less than 1 month old (-5), publisher does not match GitHub owner (-10).
Source Diff deductions: -5 per unexpected file in the npm package that is not in the GitHub repo.
| Grade | Score | Verdict | Exit Code |
|---|---|---|---|
| A | 90 -- 100 | CLEAN -- No issues detected | 0 |
| B | 80 -- 89 | CLEAN -- No issues detected | 0 |
| C | 70 -- 79 | CAUTION -- Review findings before installing | 1 |
| D | 60 -- 69 | DANGER -- Manual review strongly recommended | 2 |
| F | 0 -- 59 | DANGER -- Manual review strongly recommended | 2 |
Usage: npx-ray [options] [package]
X-ray vision for npm packages — security scanner that audits source code,
detects obfuscation, and flags supply chain risks before you install
Arguments:
package Package to scan (name, name@version, or local tarball)
Options:
--json Output results as JSON (for CI/CD pipelines)
--verbose Show detailed findings for each scanner
--mcp Scan MCP servers from editor configurations
--no-github Skip GitHub repository checks
--no-diff Skip source-vs-published diff
-V, --version Output the version number
-h, --help Display help
# Scan a package by name (resolves latest version)
npx npx-ray express
# Scan a specific version
npx npx-ray lodash@4.17.21
# JSON output for scripting or CI
npx npx-ray react --json
# Full detail with every individual finding
npx npx-ray axios --verbose
# Fast scan (skip GitHub API and source diff)
npx npx-ray leftpad --no-github --no-diff
# Scan all MCP servers configured on your machine
npx npx-ray --mcpThe --mcp flag scans your local editor configurations for MCP (Model Context Protocol) servers that use npm packages. It automatically discovers servers from:
- Claude Desktop (macOS, Windows, Linux)
- Cursor (
~/.cursor/mcp.json) - VS Code (
~/.vscode/mcp.json) - Claude Code (
~/.claude.json) - Windsurf (
~/.windsurf/mcp.json,~/.codeium/windsurf/mcp_config.json)
For each npm-based MCP server found, npx-ray:
- Checks whether the package version is pinned (e.g.,
@anthropic-ai/mcp-server@1.2.3vs unpinned@anthropic-ai/mcp-server) - Runs the full security scan on the npm package
- Reports a summary across all servers
Unpinned MCP servers are a supply chain risk -- an attacker who compromises a package can push a new version that gets auto-installed the next time your editor launches.
npx npx-ray --mcpUse npx-ray in your CI pipeline to gate new dependencies before they enter your project.
name: Dependency Security Scan
on:
pull_request:
paths:
- 'package.json'
- 'package-lock.json'
jobs:
scan:
runs-on: ubuntu-latest
strategy:
matrix:
package: [express, lodash, axios] # or parse from diff
steps:
- name: Security scan
run: npx npx-ray ${{ matrix.package }} --json| Code | Meaning | Action |
|---|---|---|
| 0 | Grade A or B -- clean | Proceed |
| 1 | Grade C -- warnings found | Review findings |
| 2 | Grade D or F -- critical issues | Block and investigate |
The --json flag outputs a structured report that can be parsed by downstream tools:
{
"package": {
"name": "example",
"version": "1.0.0",
"license": "MIT",
"publisher": "author",
"dependencies": {}
},
"scanners": [
{
"name": "static",
"passed": true,
"findings": [],
"summary": "No dangerous patterns detected"
}
],
"score": 95,
"grade": "A",
"verdict": "CLEAN — No issues detected",
"duration": 2100
}- Fetch metadata from the npm registry (version, tarball URL, publisher, dependencies)
- Download and extract the tarball to a temporary directory -- nothing is installed, no scripts run
- Run 8 scanners in parallel against the extracted source code:
- Static analysis (dangerous API patterns)
- Obfuscation detection (entropy, hex, base64, string arrays)
- Lifecycle hooks (install scripts)
- Secrets (API keys, tokens, private keys)
- Binaries (non-reviewable native addons)
- Dependency analysis (bloat, wildcards, git URLs)
- Typosquatting (Levenshtein distance against top npm packages)
- IOC extraction (URLs and IPs, with deobfuscation layer)
- Check GitHub health via the unauthenticated API (stars, age, archive status, publisher match)
- Diff source vs. published by downloading the GitHub repo tarball and comparing file lists and content hashes
- Calculate score using weighted category deductions
- Output report (colored terminal output or JSON)
- Clean up the temporary directory
No data is sent to any external service. GitHub API requests are unauthenticated and optional (skip with --no-github).
| Feature | npx-ray | npm audit | socket.dev | mcp-scan |
|---|---|---|---|---|
| Source code analysis | Yes | No | No | No |
| Obfuscation detection | Yes | No | Yes | No |
| Shannon entropy analysis | Yes | No | No | No |
| Typosquatting detection | Yes | No | Yes | No |
| Source-vs-npm diff | Yes | No | No | No |
| Lifecycle hook scanning | Yes | No | Yes | No |
| Secret detection | Yes | No | No | No |
| Binary file detection | Yes | No | No | No |
| Dependency analysis | Yes | Yes | Yes | No |
| GitHub health check | Yes | No | Yes | No |
| MCP server scanning | Yes | No | No | Yes |
| Works pre-install | Yes | No (post-install) | Yes | Yes |
| Runs locally | Yes | Yes | No (SaaS) | Yes |
| No account required | Yes | Yes | No | Yes |
| JSON output for CI | Yes | Yes | Yes | Yes |
Contributions are welcome. Please open an issue first to discuss what you would like to change.
git clone https://github.com/txdadlab/npx-ray.git
cd npx-ray
npm install
npm run build
node dist/cli.js chalk # Test your changes
npm test # Run the test suite- Create
src/scanners/your-scanner.tsimplementing theScannerResultinterface - Import and add it to the
Promise.allarray insrc/cli.ts - Add a weight entry in
src/scorer.tsunderCATEGORY_WEIGHTS - Add tests in
tests/