install-guard 1.1.1 → 1.1.2
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 +197 -120
- package/bin/cli.js +28 -7
- package/package.json +5 -1
- package/src/analyze.js +16 -10
- package/src/checks/dependencyDiff.js +114 -0
- package/src/checks/deprecation.js +24 -0
- package/src/checks/githubVerify.js +67 -0
- package/src/checks/index.js +8 -0
- package/src/checks/license.js +35 -0
- package/src/checks/maintainers.js +30 -0
- package/src/checks/recentPublish.js +70 -0
- package/src/checks/scripts.js +45 -0
- package/src/checks/typosquat.js +77 -0
- package/src/format.js +143 -87
- package/src/index.js +2 -2
- package/src/install.js +16 -10
- package/src/scan.js +13 -11
- package/src/services/pipeline.js +105 -0
- package/src/services/scorer.js +36 -0
- package/src/utils/cache.js +44 -0
- package/src/utils/github.js +64 -0
- package/src/utils/registry.js +99 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# 🚨 Should You Trust That npm Package Before Installing?
|
|
6
6
|
|
|
7
|
-
**install-guard**
|
|
7
|
+
**install-guard** is a supply chain security tool that analyzes npm packages for risks **before** you install them. It detects compromised versions, typosquatting, suspicious dependencies, and more.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -16,41 +16,50 @@ $ npx install-guard install some-random-lib
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```
|
|
19
|
-
|
|
19
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
20
20
|
📦 some-random-lib v0.1.3
|
|
21
21
|
A random utility library
|
|
22
|
-
|
|
22
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
23
23
|
|
|
24
|
-
Risk
|
|
25
|
-
|
|
24
|
+
Risk Level: 💀 CRITICAL (9/10)
|
|
25
|
+
█████████░
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
──────────────────────────────────────────────────────────
|
|
28
28
|
|
|
29
29
|
📊 Package Info
|
|
30
30
|
|
|
31
|
-
Downloads (weekly)
|
|
32
|
-
Maintainers
|
|
33
|
-
License
|
|
34
|
-
|
|
35
|
-
Dependencies
|
|
36
|
-
Versions
|
|
31
|
+
Downloads (weekly) 120
|
|
32
|
+
Maintainers unknown-person
|
|
33
|
+
License Unknown
|
|
34
|
+
Published 6 hours ago
|
|
35
|
+
Dependencies 3
|
|
36
|
+
Total Versions 2
|
|
37
|
+
Repository ✘ None
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
──────────────────────────────────────────────────────────
|
|
39
40
|
|
|
40
|
-
🔍
|
|
41
|
+
🔍 Findings
|
|
41
42
|
|
|
42
|
-
✘
|
|
43
|
-
|
|
44
|
-
✘
|
|
45
|
-
|
|
46
|
-
✘
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
✘ Version 0.1.3 was published 6 hours ago
|
|
44
|
+
✘ Lifecycle scripts found: postinstall
|
|
45
|
+
✘ New dep "plain-crypto-js" looks like typosquat of "crypto-js"
|
|
46
|
+
✘ New dep "plain-crypto-js" has very low downloads (12/week)
|
|
47
|
+
✘ No GitHub tag found for version 0.1.3
|
|
48
|
+
✘ No license specified
|
|
49
|
+
⚠ Single maintainer: unknown-person
|
|
50
|
+
⚠ No recent commits in the last 90 days
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
──────────────────────────────────────────────────────────
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
💡 Recommendation
|
|
55
|
+
|
|
56
|
+
⛔ Avoid installing some-random-lib@0.1.3
|
|
57
|
+
✔ Safe alternative: some-random-lib@0.1.1
|
|
58
|
+
No install scripts, published > 7 days ago
|
|
59
|
+
|
|
60
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
61
|
+
|
|
62
|
+
💀 CRITICAL risk. Are you absolutely sure? (y/n):
|
|
54
63
|
```
|
|
55
64
|
|
|
56
65
|
</details>
|
|
@@ -59,11 +68,12 @@ $ npx install-guard install some-random-lib
|
|
|
59
68
|
|
|
60
69
|
## 😬 The Problem
|
|
61
70
|
|
|
62
|
-
|
|
71
|
+
Supply chain attacks on npm are increasing:
|
|
63
72
|
|
|
64
73
|
- Malicious packages are published **daily**
|
|
65
|
-
- Popular packages get compromised
|
|
66
|
-
-
|
|
74
|
+
- Popular packages get compromised (e.g., event-stream, ua-parser-js, colors)
|
|
75
|
+
- Attackers inject postinstall scripts, add typosquatted dependencies, or hijack maintainer accounts
|
|
76
|
+
- `npm audit` only checks **known CVEs** — it can't detect a new attack in progress
|
|
67
77
|
|
|
68
78
|
You shouldn't have to guess if a package is safe.
|
|
69
79
|
|
|
@@ -71,19 +81,25 @@ You shouldn't have to guess if a package is safe.
|
|
|
71
81
|
|
|
72
82
|
## 🛡️ The Solution
|
|
73
83
|
|
|
74
|
-
install-guard
|
|
84
|
+
install-guard runs a **modular detection pipeline** with 8 specialized security checks, GitHub verification, dependency diffing, and a weighted risk scorer — all before a single file is downloaded.
|
|
75
85
|
|
|
76
86
|
---
|
|
77
87
|
|
|
78
88
|
## ⚡ Quick Start
|
|
79
89
|
|
|
80
|
-
Analyze
|
|
90
|
+
Analyze any package:
|
|
81
91
|
|
|
82
92
|
```bash
|
|
83
93
|
npx install-guard axios
|
|
84
94
|
```
|
|
85
95
|
|
|
86
|
-
|
|
96
|
+
Analyze a specific version:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx install-guard axios@1.14.0
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Safely install with pre-check:
|
|
87
103
|
|
|
88
104
|
```bash
|
|
89
105
|
npx install-guard install axios
|
|
@@ -95,122 +111,196 @@ Scan your entire project:
|
|
|
95
111
|
npx install-guard scan
|
|
96
112
|
```
|
|
97
113
|
|
|
98
|
-
Scan with
|
|
114
|
+
Scan with full details per package:
|
|
99
115
|
|
|
100
116
|
```bash
|
|
101
117
|
npx install-guard scan --verbose
|
|
102
118
|
```
|
|
103
119
|
|
|
120
|
+
Get JSON output (for CI/CD):
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npx install-guard axios --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Skip GitHub checks (faster):
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx install-guard axios --skip-github
|
|
130
|
+
```
|
|
131
|
+
|
|
104
132
|
---
|
|
105
133
|
|
|
106
|
-
## 🧠
|
|
134
|
+
## 🧠 Detection Pipeline
|
|
135
|
+
|
|
136
|
+
install-guard runs **8 isolated checks** through a security pipeline:
|
|
137
|
+
|
|
138
|
+
| # | Check | What it detects | Risk Weight |
|
|
139
|
+
|---|-------|----------------|-------------|
|
|
140
|
+
| 1 | **Recent Publish** | Version published <24h ago, unusual version jumps, rapid publish cadence | +2 to +5 |
|
|
141
|
+
| 2 | **Dependency Diff** | New/removed deps between versions, deep analysis of each new dep | +3 to +5 |
|
|
142
|
+
| 3 | **Script Analysis** | `preinstall`/`postinstall` hooks, suspicious commands (curl, eval, base64) | +5 |
|
|
143
|
+
| 4 | **Typosquatting** | Package name similar to popular packages (Levenshtein distance) | +5 |
|
|
144
|
+
| 5 | **Maintainer Analysis** | No maintainers, single maintainer | +1 to +3 |
|
|
145
|
+
| 6 | **License Check** | Missing, unknown, or non-permissive licenses | +1 to +3 |
|
|
146
|
+
| 7 | **GitHub Verification** | No matching tag/release, no recent commits in repo | +2 to +4 |
|
|
147
|
+
| 8 | **Deprecation** | Package flagged as deprecated on npm | +3 |
|
|
148
|
+
|
|
149
|
+
Results are scored and normalized to **0–10** with labels: `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`.
|
|
150
|
+
|
|
151
|
+
### Dependency Diff Engine
|
|
152
|
+
|
|
153
|
+
When you check a specific version, install-guard compares its dependencies against the previous version. For each **newly added** dependency, it runs sub-analysis:
|
|
107
154
|
|
|
108
|
-
|
|
155
|
+
- Download count check
|
|
156
|
+
- Publish age check
|
|
157
|
+
- Typosquatting detection
|
|
158
|
+
- Install script detection
|
|
109
159
|
|
|
110
|
-
|
|
111
|
-
|-------|----------------|
|
|
112
|
-
| 📉 **Downloads** | Low popularity = higher risk |
|
|
113
|
-
| 🕒 **Last Updated** | Abandoned packages |
|
|
114
|
-
| ⚠ **Install Scripts** | `preinstall` / `postinstall` hooks (common attack vector) |
|
|
115
|
-
| 👥 **Maintainers** | Single or no maintainers |
|
|
116
|
-
| 📜 **License** | Missing or non-permissive licenses |
|
|
117
|
-
| 🔗 **Repository** | No source code link |
|
|
118
|
-
| 📅 **Package Age** | Brand new packages (< 30 days) |
|
|
119
|
-
| 📦 **Dependencies** | High dependency count |
|
|
120
|
-
| 🚫 **Deprecated** | Flagged as deprecated on npm |
|
|
121
|
-
| 🧠 **Typosquatting** | Names suspiciously similar to popular packages |
|
|
160
|
+
This catches the exact pattern used in real supply chain attacks (e.g., injecting a malicious dependency in a patch release).
|
|
122
161
|
|
|
123
|
-
|
|
162
|
+
### Safe Version Recommendation
|
|
163
|
+
|
|
164
|
+
If a version is flagged as risky, install-guard scans all previous versions and suggests the latest safe one — no install scripts, published more than 7 days ago.
|
|
124
165
|
|
|
125
166
|
---
|
|
126
167
|
|
|
127
|
-
## 📊 Example
|
|
168
|
+
## 📊 Example Output
|
|
128
169
|
|
|
129
170
|
### ✅ Safe package
|
|
130
171
|
|
|
131
172
|
```
|
|
132
|
-
|
|
133
|
-
📦 axios v1.
|
|
173
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
174
|
+
📦 axios v1.15.0
|
|
175
|
+
Promise based HTTP client for the browser and node.js
|
|
176
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
134
177
|
|
|
135
|
-
Risk
|
|
178
|
+
Risk Level: ✅ LOW (1/10)
|
|
136
179
|
█░░░░░░░░░
|
|
137
180
|
|
|
138
|
-
🔍
|
|
139
|
-
✔
|
|
140
|
-
✔
|
|
141
|
-
✔
|
|
142
|
-
✔
|
|
143
|
-
✔
|
|
144
|
-
✔
|
|
145
|
-
✔
|
|
146
|
-
✔
|
|
147
|
-
|
|
181
|
+
🔍 Findings
|
|
182
|
+
✔ Published 6 days ago
|
|
183
|
+
✔ No lifecycle scripts
|
|
184
|
+
✔ No typosquatting detected
|
|
185
|
+
✔ License: MIT
|
|
186
|
+
✔ Not deprecated
|
|
187
|
+
✔ No dependency changes from previous version
|
|
188
|
+
✔ GitHub tag found for v1.15.0
|
|
189
|
+
✔ Repository has recent activity
|
|
190
|
+
⚠ Single maintainer: jasonsaayman
|
|
191
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
148
192
|
```
|
|
149
193
|
|
|
150
|
-
### 🚨
|
|
194
|
+
### 🚨 Compromised package (simulated)
|
|
151
195
|
|
|
152
196
|
```
|
|
153
|
-
|
|
154
|
-
📦
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
✘
|
|
162
|
-
✘
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
197
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
198
|
+
📦 evil-lib v2.0.0
|
|
199
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
200
|
+
|
|
201
|
+
Risk Level: 💀 CRITICAL (9/10)
|
|
202
|
+
█████████░
|
|
203
|
+
|
|
204
|
+
🔍 Findings
|
|
205
|
+
✘ Version 2.0.0 was published 3 hours ago
|
|
206
|
+
✘ Lifecycle scripts found: postinstall
|
|
207
|
+
✘ Script "postinstall" contains suspicious command: curl http://...
|
|
208
|
+
✘ New dependencies added: plain-crypto-js
|
|
209
|
+
✘ New dep "plain-crypto-js" looks like typosquat of "crypto-js"
|
|
210
|
+
✘ New dep "plain-crypto-js" has very low downloads (0/week)
|
|
211
|
+
✘ No GitHub tag found for version 2.0.0
|
|
212
|
+
|
|
213
|
+
💡 Recommendation
|
|
214
|
+
⛔ Avoid installing evil-lib@2.0.0
|
|
215
|
+
✔ Safe alternative: evil-lib@1.9.2
|
|
216
|
+
No install scripts, published > 7 days ago
|
|
217
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
166
218
|
```
|
|
167
219
|
|
|
168
220
|
### 📋 Project scan
|
|
169
221
|
|
|
170
222
|
```
|
|
171
|
-
|
|
223
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
172
224
|
📋 Dependency Scan Summary
|
|
173
|
-
|
|
225
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
174
226
|
|
|
175
|
-
Package
|
|
227
|
+
Package Score Level
|
|
176
228
|
────────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
old-utils 5/10
|
|
179
|
-
express 0/10
|
|
180
|
-
axios 1/10
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
229
|
+
evil-lib 9/10 💀 Critical
|
|
230
|
+
old-utils 5/10 ⚠ Medium
|
|
231
|
+
express 0/10 ✅ Low
|
|
232
|
+
axios 1/10 ✅ Low
|
|
233
|
+
|
|
234
|
+
──────────────────────────────────────────────────────────
|
|
235
|
+
Total: 4 packages 2 low · 1 medium · 0 high · 1 critical
|
|
236
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 🏗️ Architecture
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
src/
|
|
245
|
+
├── checks/ # Isolated security checks
|
|
246
|
+
│ ├── recentPublish.js # Publish timing analysis
|
|
247
|
+
│ ├── dependencyDiff.js # Version-to-version dep comparison
|
|
248
|
+
│ ├── scripts.js # Lifecycle script detection
|
|
249
|
+
│ ├── typosquat.js # Name similarity analysis
|
|
250
|
+
│ ├── maintainers.js # Maintainer signals
|
|
251
|
+
│ ├── license.js # License validation
|
|
252
|
+
│ ├── githubVerify.js # GitHub tag/release verification
|
|
253
|
+
│ └── deprecation.js # Deprecation detection
|
|
254
|
+
├── services/
|
|
255
|
+
│ ├── pipeline.js # Orchestrates all checks
|
|
256
|
+
│ └── scorer.js # Weighted risk scoring
|
|
257
|
+
├── utils/
|
|
258
|
+
│ ├── registry.js # npm registry API + caching
|
|
259
|
+
│ ├── github.js # GitHub API integration
|
|
260
|
+
│ └── cache.js # File-based cache (15min TTL)
|
|
261
|
+
├── format.js # CLI output formatter
|
|
262
|
+
├── scan.js # Project-wide scanner
|
|
263
|
+
├── install.js # Safe install with prompts
|
|
264
|
+
└── index.js # Entry point
|
|
185
265
|
```
|
|
186
266
|
|
|
267
|
+
Each check is **fully isolated** — returns a standardized `{ id, findings[] }` structure. The pipeline runs sync checks immediately and async checks (GitHub, dependency deep-analysis) in parallel.
|
|
268
|
+
|
|
187
269
|
---
|
|
188
270
|
|
|
189
271
|
## ✨ Features
|
|
190
272
|
|
|
191
|
-
- 🔍 **
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
273
|
+
- 🔍 **8 security checks** — modular detection pipeline
|
|
274
|
+
- 🔄 **Dependency diff engine** — compares deps between versions
|
|
275
|
+
- 🧠 **Typosquatting detection** — Levenshtein distance against 80+ popular packages
|
|
276
|
+
- 🏷️ **GitHub verification** — checks for matching tags and recent activity
|
|
277
|
+
- ⚠ **Script analysis** — detects dangerous commands in lifecycle scripts
|
|
278
|
+
- 💡 **Safe version suggestions** — recommends the latest clean version
|
|
279
|
+
- 📊 **Weighted risk scoring** — 0–10 with LOW/MEDIUM/HIGH/CRITICAL labels
|
|
280
|
+
- 🛑 **Install blocking** — prompts before installing risky packages
|
|
281
|
+
- 📋 **Project scan** — audit all deps with summary table
|
|
282
|
+
- 🗄️ **File-based caching** — avoids redundant API calls (15min TTL)
|
|
283
|
+
- 📤 **JSON output** — pipe results into CI/CD pipelines
|
|
284
|
+
- ⚡ **Zero config** — works with `npx`, no setup required
|
|
199
285
|
|
|
200
286
|
---
|
|
201
287
|
|
|
202
288
|
## 🤔 Why not npm audit?
|
|
203
289
|
|
|
204
|
-
| Feature
|
|
205
|
-
|
|
206
|
-
| Known vulnerabilities
|
|
207
|
-
|
|
|
208
|
-
| Pre-install
|
|
209
|
-
|
|
|
210
|
-
| Typosquatting
|
|
211
|
-
|
|
|
212
|
-
|
|
|
213
|
-
|
|
|
290
|
+
| Feature | npm audit | install-guard |
|
|
291
|
+
|----------------------------|-----------|---------------|
|
|
292
|
+
| Known CVE vulnerabilities | ✅ | — |
|
|
293
|
+
| Supply chain attack detection | ❌ | ✅ |
|
|
294
|
+
| Pre-install analysis | ❌ | ✅ |
|
|
295
|
+
| Dependency diff | ❌ | ✅ |
|
|
296
|
+
| Typosquatting detection | ❌ | ✅ |
|
|
297
|
+
| Script analysis | ❌ | ✅ |
|
|
298
|
+
| GitHub verification | ❌ | ✅ |
|
|
299
|
+
| Install blocking | ❌ | ✅ |
|
|
300
|
+
| Safe version suggestions | ❌ | ✅ |
|
|
301
|
+
| JSON output for CI | ✅ | ✅ |
|
|
302
|
+
|
|
303
|
+
**Use both together** — `npm audit` for known vulnerabilities, `install-guard` for everything else.
|
|
214
304
|
|
|
215
305
|
---
|
|
216
306
|
|
|
@@ -224,12 +314,12 @@ npm install -g install-guard
|
|
|
224
314
|
|
|
225
315
|
## 🔮 Roadmap
|
|
226
316
|
|
|
227
|
-
- 🔍 GitHub activity analysis
|
|
228
|
-
- 🧠 Advanced typosquatting (permutations, homoglyphs)
|
|
229
317
|
- 📊 Dependency tree visualization
|
|
230
318
|
- 🔌 CI/CD integration (exit codes for pipelines)
|
|
231
|
-
- 🏷️ `.install-guardrc` config for custom thresholds
|
|
232
|
-
- 📝
|
|
319
|
+
- 🏷️ `.install-guardrc` config for custom risk thresholds
|
|
320
|
+
- 📝 HTML/CSV report export
|
|
321
|
+
- 🧠 Advanced typosquatting (permutations, homoglyphs, scope confusion)
|
|
322
|
+
- 🔔 Webhook notifications for risky dependencies
|
|
233
323
|
|
|
234
324
|
---
|
|
235
325
|
|
|
@@ -243,16 +333,3 @@ PRs welcome! Let's make npm safer together.
|
|
|
243
333
|
|
|
244
334
|
If you find this useful, consider giving it a star ⭐
|
|
245
335
|
It helps others discover the project!
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
## 🤝 Contributing
|
|
250
|
-
|
|
251
|
-
PRs welcome! Let's make npm safer together.
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## ⭐ Support
|
|
256
|
-
|
|
257
|
-
If you find this useful, consider giving it a star ⭐
|
|
258
|
-
It helps others discover the project!
|
package/bin/cli.js
CHANGED
|
@@ -8,25 +8,46 @@ const program = new Command();
|
|
|
8
8
|
|
|
9
9
|
program
|
|
10
10
|
.name("install-guard")
|
|
11
|
-
.description("
|
|
12
|
-
.version("
|
|
11
|
+
.description("Detect supply chain attacks in npm packages before installing")
|
|
12
|
+
.version("3.0.0");
|
|
13
13
|
|
|
14
14
|
program
|
|
15
|
-
.argument("[package]", "package name to analyze")
|
|
16
|
-
.
|
|
15
|
+
.argument("[package]", "package name to analyze (e.g., axios or axios@1.14.1)")
|
|
16
|
+
.option("--json", "Output results as JSON")
|
|
17
|
+
.option("--skip-github", "Skip GitHub verification (faster)")
|
|
18
|
+
.action(async (pkg, opts) => {
|
|
17
19
|
if (!pkg) {
|
|
18
20
|
program.help();
|
|
19
21
|
return;
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
|
|
24
|
+
// Support package@version syntax
|
|
25
|
+
let name = pkg;
|
|
26
|
+
let version;
|
|
27
|
+
const atIndex = pkg.lastIndexOf("@");
|
|
28
|
+
if (atIndex > 0) {
|
|
29
|
+
name = pkg.slice(0, atIndex);
|
|
30
|
+
version = pkg.slice(atIndex + 1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await analyzePackage(name, version, {
|
|
34
|
+
json: opts.json,
|
|
35
|
+
skipGithub: opts.skipGithub,
|
|
36
|
+
});
|
|
22
37
|
});
|
|
23
38
|
|
|
24
39
|
program
|
|
25
40
|
.command("scan")
|
|
26
|
-
.description("Scan all project dependencies for risks")
|
|
41
|
+
.description("Scan all project dependencies for supply chain risks")
|
|
27
42
|
.option("-v, --verbose", "Show detailed analysis for each package")
|
|
43
|
+
.option("--json", "Output results as JSON")
|
|
44
|
+
.option("--skip-github", "Skip GitHub verification (faster)")
|
|
28
45
|
.action(async (opts) => {
|
|
29
|
-
await scanProject({
|
|
46
|
+
await scanProject({
|
|
47
|
+
verbose: opts.verbose,
|
|
48
|
+
json: opts.json,
|
|
49
|
+
skipGithub: opts.skipGithub,
|
|
50
|
+
});
|
|
30
51
|
});
|
|
31
52
|
|
|
32
53
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "install-guard",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"install-guard": "./bin/cli.js"
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
11
|
},
|
|
12
12
|
"author": "Sarthak Kumar Sahoo",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/SarthakSahoo1407/install-guard.git"
|
|
16
|
+
},
|
|
13
17
|
"license": "MIT",
|
|
14
18
|
"description": "Analyze npm packages for security risks before installing",
|
|
15
19
|
"dependencies": {
|
package/src/analyze.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import ora from "ora";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { formatAnalysis } from "./format.js";
|
|
2
|
+
import { runPipeline } from "./services/pipeline.js";
|
|
3
|
+
import { formatPipelineResult } from "./format.js";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Analyze a package and print results to stdout.
|
|
7
|
+
* Returns the structured result.
|
|
8
|
+
*/
|
|
9
|
+
export async function analyze(pkgName, version, opts = {}) {
|
|
10
|
+
const spinner = ora(`Analyzing ${pkgName}${version ? `@${version}` : ""}...`).start();
|
|
8
11
|
|
|
9
12
|
try {
|
|
10
|
-
const
|
|
11
|
-
const result = calculateRisk(data);
|
|
12
|
-
|
|
13
|
+
const result = await runPipeline(pkgName, version, opts);
|
|
13
14
|
spinner.stop();
|
|
14
|
-
console.log(formatAnalysis(data, result));
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
console.log(JSON.stringify(result, null, 2));
|
|
18
|
+
} else {
|
|
19
|
+
console.log(formatPipelineResult(result));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return result;
|
|
17
23
|
} catch (err) {
|
|
18
24
|
spinner.fail(`Failed to analyze "${pkgName}": ${err.message}`);
|
|
19
25
|
return null;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getDownloads, getRegistryData } from "../utils/registry.js";
|
|
2
|
+
import { checkTyposquatName } from "./typosquat.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compares dependencies between the current and previous version.
|
|
6
|
+
* Flags newly added dependencies and analyzes them for suspiciousness.
|
|
7
|
+
*/
|
|
8
|
+
export async function checkDependencyDiff(ctx) {
|
|
9
|
+
const findings = [];
|
|
10
|
+
|
|
11
|
+
const current = ctx.dependencies;
|
|
12
|
+
const previous = ctx.previousDependencies;
|
|
13
|
+
|
|
14
|
+
const added = Object.keys(current).filter((d) => !(d in previous));
|
|
15
|
+
const removed = Object.keys(previous).filter((d) => !(d in current));
|
|
16
|
+
|
|
17
|
+
if (added.length === 0 && removed.length === 0) {
|
|
18
|
+
findings.push({
|
|
19
|
+
severity: "info",
|
|
20
|
+
message: "No dependency changes from previous version",
|
|
21
|
+
score: 0,
|
|
22
|
+
});
|
|
23
|
+
return { id: "dependency-diff", findings };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (removed.length > 0) {
|
|
27
|
+
findings.push({
|
|
28
|
+
severity: "info",
|
|
29
|
+
message: `Removed dependencies: ${removed.join(", ")}`,
|
|
30
|
+
score: 0,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (added.length > 0) {
|
|
35
|
+
findings.push({
|
|
36
|
+
severity: "high",
|
|
37
|
+
message: `New dependencies added: ${added.join(", ")}`,
|
|
38
|
+
score: 3,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Deep-analyze each newly added dependency
|
|
42
|
+
for (const dep of added) {
|
|
43
|
+
const subFindings = await analyzeSuspiciousDep(dep);
|
|
44
|
+
findings.push(...subFindings);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { id: "dependency-diff", findings };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function analyzeSuspiciousDep(name) {
|
|
52
|
+
const findings = [];
|
|
53
|
+
|
|
54
|
+
// Typosquatting check
|
|
55
|
+
const typo = checkTyposquatName(name);
|
|
56
|
+
if (typo) {
|
|
57
|
+
findings.push({
|
|
58
|
+
severity: "critical",
|
|
59
|
+
message: `New dep "${name}" looks like typosquat of "${typo}"`,
|
|
60
|
+
score: 5,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const registry = await getRegistryData(name);
|
|
66
|
+
const downloads = await getDownloads(name);
|
|
67
|
+
const latest = registry["dist-tags"]?.latest;
|
|
68
|
+
const publishTime = registry.time?.[latest];
|
|
69
|
+
|
|
70
|
+
// Low downloads
|
|
71
|
+
if (downloads.downloads < 500) {
|
|
72
|
+
findings.push({
|
|
73
|
+
severity: "high",
|
|
74
|
+
message: `New dep "${name}" has very low downloads (${downloads.downloads}/week)`,
|
|
75
|
+
score: 4,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Very new package
|
|
80
|
+
if (publishTime) {
|
|
81
|
+
const ageDays =
|
|
82
|
+
(Date.now() - new Date(publishTime).getTime()) / (1000 * 60 * 60 * 24);
|
|
83
|
+
if (ageDays < 30) {
|
|
84
|
+
findings.push({
|
|
85
|
+
severity: "high",
|
|
86
|
+
message: `New dep "${name}" is less than 30 days old`,
|
|
87
|
+
score: 4,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Has install scripts
|
|
93
|
+
const versionData = registry.versions?.[latest];
|
|
94
|
+
if (
|
|
95
|
+
versionData?.scripts?.postinstall ||
|
|
96
|
+
versionData?.scripts?.preinstall ||
|
|
97
|
+
versionData?.scripts?.install
|
|
98
|
+
) {
|
|
99
|
+
findings.push({
|
|
100
|
+
severity: "critical",
|
|
101
|
+
message: `New dep "${name}" has install scripts`,
|
|
102
|
+
score: 5,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
findings.push({
|
|
107
|
+
severity: "high",
|
|
108
|
+
message: `New dep "${name}" could not be resolved on npm`,
|
|
109
|
+
score: 4,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return findings;
|
|
114
|
+
}
|