install-guard 1.0.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/LICENSE +21 -0
- package/README.md +229 -52
- package/bin/cli.js +40 -14
- package/package.json +13 -4
- package/src/analyze.js +15 -22
- 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 +258 -0
- package/src/index.js +2 -2
- package/src/install.js +43 -15
- package/src/npm.js +31 -16
- package/src/scan.js +55 -5
- package/src/score.js +107 -26
- package/src/services/pipeline.js +105 -0
- package/src/services/scorer.js +36 -0
- package/src/typosquat.js +50 -0
- package/src/utils/cache.js +44 -0
- package/src/utils/github.js +64 -0
- package/src/utils/registry.js +99 -0
- package/install-guard-1.0.0.tgz +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sarthak Kumar Sahoo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,27 +1,65 @@
|
|
|
1
|
+

|
|
2
|
+

|
|
3
|
+

|
|
4
|
+
|
|
1
5
|
# šØ Should You Trust That npm Package Before Installing?
|
|
2
6
|
|
|
3
|
-
**install-guard** analyzes npm packages
|
|
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.
|
|
4
8
|
|
|
5
9
|
---
|
|
6
10
|
|
|
7
11
|
<details>
|
|
8
|
-
<summary
|
|
12
|
+
<summary>š¦ See it in action</summary>
|
|
9
13
|
|
|
10
14
|
```bash
|
|
11
|
-
npx install-guard install some-random-lib
|
|
15
|
+
$ npx install-guard install some-random-lib
|
|
12
16
|
```
|
|
13
17
|
|
|
14
18
|
```
|
|
15
|
-
|
|
19
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
20
|
+
š¦ some-random-lib v0.1.3
|
|
21
|
+
A random utility library
|
|
22
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
23
|
+
|
|
24
|
+
Risk Level: š CRITICAL (9/10)
|
|
25
|
+
āāāāāāāāāā
|
|
26
|
+
|
|
27
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
28
|
+
|
|
29
|
+
š Package Info
|
|
30
|
+
|
|
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
|
|
38
|
+
|
|
39
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
16
40
|
|
|
17
|
-
|
|
18
|
-
Risk Score: 8/10 šØ High Risk
|
|
41
|
+
š Findings
|
|
19
42
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
23
51
|
|
|
24
|
-
|
|
52
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
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):
|
|
25
63
|
```
|
|
26
64
|
|
|
27
65
|
</details>
|
|
@@ -30,11 +68,12 @@ Risk Score: 8/10 šØ High Risk
|
|
|
30
68
|
|
|
31
69
|
## š¬ The Problem
|
|
32
70
|
|
|
33
|
-
|
|
71
|
+
Supply chain attacks on npm are increasing:
|
|
34
72
|
|
|
35
|
-
- Malicious packages are published daily
|
|
36
|
-
- Popular packages get compromised
|
|
37
|
-
-
|
|
73
|
+
- Malicious packages are published **daily**
|
|
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
|
|
38
77
|
|
|
39
78
|
You shouldn't have to guess if a package is safe.
|
|
40
79
|
|
|
@@ -42,90 +81,226 @@ You shouldn't have to guess if a package is safe.
|
|
|
42
81
|
|
|
43
82
|
## š”ļø The Solution
|
|
44
83
|
|
|
45
|
-
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.
|
|
46
85
|
|
|
47
86
|
---
|
|
48
87
|
|
|
49
88
|
## ā” Quick Start
|
|
50
89
|
|
|
51
|
-
|
|
90
|
+
Analyze any package:
|
|
52
91
|
|
|
53
92
|
```bash
|
|
54
93
|
npx install-guard axios
|
|
55
94
|
```
|
|
56
95
|
|
|
57
|
-
|
|
96
|
+
Analyze a specific version:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx install-guard axios@1.14.0
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Safely install with pre-check:
|
|
58
103
|
|
|
59
104
|
```bash
|
|
60
105
|
npx install-guard install axios
|
|
61
106
|
```
|
|
62
107
|
|
|
63
|
-
Scan your project:
|
|
108
|
+
Scan your entire project:
|
|
64
109
|
|
|
65
110
|
```bash
|
|
66
111
|
npx install-guard scan
|
|
67
112
|
```
|
|
68
113
|
|
|
114
|
+
Scan with full details per package:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx install-guard scan --verbose
|
|
118
|
+
```
|
|
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
|
+
|
|
69
132
|
---
|
|
70
133
|
|
|
71
|
-
## š§
|
|
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:
|
|
72
154
|
|
|
73
|
-
|
|
155
|
+
- Download count check
|
|
156
|
+
- Publish age check
|
|
157
|
+
- Typosquatting detection
|
|
158
|
+
- Install script detection
|
|
74
159
|
|
|
75
|
-
|
|
76
|
-
- š Last update time
|
|
77
|
-
- ā Install/postinstall scripts
|
|
78
|
-
- š¦ Version activity
|
|
160
|
+
This catches the exact pattern used in real supply chain attacks (e.g., injecting a malicious dependency in a patch release).
|
|
79
161
|
|
|
80
|
-
|
|
162
|
+
### Safe Version Recommendation
|
|
81
163
|
|
|
82
|
-
|
|
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.
|
|
83
165
|
|
|
84
166
|
---
|
|
85
167
|
|
|
86
|
-
## š Example
|
|
168
|
+
## š Example Output
|
|
87
169
|
|
|
88
170
|
### ā
Safe package
|
|
89
171
|
|
|
90
172
|
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
173
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
174
|
+
š¦ axios v1.15.0
|
|
175
|
+
Promise based HTTP client for the browser and node.js
|
|
176
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
177
|
+
|
|
178
|
+
Risk Level: ā
LOW (1/10)
|
|
179
|
+
āāāāāāāāāā
|
|
180
|
+
|
|
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
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
94
192
|
```
|
|
95
193
|
|
|
96
|
-
|
|
194
|
+
### šØ Compromised package (simulated)
|
|
97
195
|
|
|
98
|
-
|
|
196
|
+
```
|
|
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
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
218
|
+
```
|
|
99
219
|
|
|
220
|
+
### š Project scan
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
224
|
+
š Dependency Scan Summary
|
|
225
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
226
|
+
|
|
227
|
+
Package Score Level
|
|
228
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
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
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
100
237
|
```
|
|
101
|
-
some-lib
|
|
102
|
-
Downloads: 200
|
|
103
|
-
Risk: 8/10
|
|
104
238
|
|
|
105
|
-
|
|
106
|
-
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## šļø Architecture
|
|
242
|
+
|
|
107
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
|
|
265
|
+
```
|
|
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.
|
|
108
268
|
|
|
109
269
|
---
|
|
110
270
|
|
|
111
271
|
## ⨠Features
|
|
112
272
|
|
|
113
|
-
- š
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
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
|
|
118
285
|
|
|
119
286
|
---
|
|
120
287
|
|
|
121
288
|
## š¤ Why not npm audit?
|
|
122
289
|
|
|
123
|
-
| Feature
|
|
124
|
-
|
|
125
|
-
| Known vulnerabilities
|
|
126
|
-
|
|
|
127
|
-
| Pre-install
|
|
128
|
-
|
|
|
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.
|
|
129
304
|
|
|
130
305
|
---
|
|
131
306
|
|
|
@@ -139,10 +314,12 @@ npm install -g install-guard
|
|
|
139
314
|
|
|
140
315
|
## š® Roadmap
|
|
141
316
|
|
|
142
|
-
- š GitHub activity analysis
|
|
143
|
-
- š§ Typosquatting detection
|
|
144
317
|
- š Dependency tree visualization
|
|
145
|
-
- š CI/CD integration
|
|
318
|
+
- š CI/CD integration (exit codes for pipelines)
|
|
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
|
|
146
323
|
|
|
147
324
|
---
|
|
148
325
|
|
|
@@ -154,5 +331,5 @@ PRs welcome! Let's make npm safer together.
|
|
|
154
331
|
|
|
155
332
|
## ā Support
|
|
156
333
|
|
|
157
|
-
If you find this useful, consider giving it a star ā
|
|
334
|
+
If you find this useful, consider giving it a star ā
|
|
158
335
|
It helps others discover the project!
|
package/bin/cli.js
CHANGED
|
@@ -8,28 +8,54 @@ const program = new Command();
|
|
|
8
8
|
|
|
9
9
|
program
|
|
10
10
|
.name("install-guard")
|
|
11
|
-
.description("
|
|
11
|
+
.description("Detect supply chain attacks in npm packages before installing")
|
|
12
|
+
.version("3.0.0");
|
|
12
13
|
|
|
13
14
|
program
|
|
14
|
-
.argument("[package]", "package name to analyze")
|
|
15
|
-
.
|
|
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) => {
|
|
16
19
|
if (!pkg) {
|
|
17
|
-
|
|
20
|
+
program.help();
|
|
18
21
|
return;
|
|
19
22
|
}
|
|
20
|
-
|
|
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
|
+
});
|
|
21
37
|
});
|
|
22
38
|
|
|
23
39
|
program
|
|
24
40
|
.command("scan")
|
|
25
|
-
.description("Scan
|
|
26
|
-
.
|
|
27
|
-
|
|
41
|
+
.description("Scan all project dependencies for supply chain risks")
|
|
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)")
|
|
45
|
+
.action(async (opts) => {
|
|
46
|
+
await scanProject({
|
|
47
|
+
verbose: opts.verbose,
|
|
48
|
+
json: opts.json,
|
|
49
|
+
skipGithub: opts.skipGithub,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
28
53
|
program
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
54
|
+
.command("install <pkg>")
|
|
55
|
+
.description("Analyze and safely install a package")
|
|
56
|
+
.action(async (pkg) => {
|
|
57
|
+
const { analyzeAndPrompt } = await import("../src/install.js");
|
|
58
|
+
await analyzeAndPrompt(pkg);
|
|
59
|
+
});
|
|
60
|
+
|
|
35
61
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "install-guard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"install-guard": "./bin/cli.js"
|
|
7
7
|
},
|
|
8
|
-
|
|
9
8
|
"type": "module",
|
|
10
9
|
"scripts": {
|
|
11
10
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
11
|
},
|
|
13
12
|
"author": "Sarthak Kumar Sahoo",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/SarthakSahoo1407/install-guard.git"
|
|
16
|
+
},
|
|
14
17
|
"license": "MIT",
|
|
15
|
-
"description": "
|
|
18
|
+
"description": "Analyze npm packages for security risks before installing",
|
|
16
19
|
"dependencies": {
|
|
17
20
|
"chalk": "^5.6.2",
|
|
18
21
|
"commander": "^14.0.3",
|
|
19
22
|
"ora": "^9.3.0"
|
|
20
23
|
},
|
|
21
|
-
"keywords": [
|
|
24
|
+
"keywords": [
|
|
25
|
+
"npm",
|
|
26
|
+
"security",
|
|
27
|
+
"cli",
|
|
28
|
+
"dependency",
|
|
29
|
+
"audit"
|
|
30
|
+
]
|
|
22
31
|
}
|
package/src/analyze.js
CHANGED
|
@@ -1,34 +1,27 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import ora from "ora";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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 { score, warnings } = calculateRisk(data);
|
|
12
|
-
|
|
13
|
+
const result = await runPipeline(pkgName, version, opts);
|
|
13
14
|
spinner.stop();
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
console.log(`Downloads (weekly): ${data.downloads.toLocaleString()}`);
|
|
18
|
-
console.log(`Risk Score: ${score}/10`);
|
|
19
|
-
|
|
20
|
-
if (score <= 3) {
|
|
21
|
-
console.log(chalk.green("ā
Low risk"));
|
|
22
|
-
} else if (score <= 6) {
|
|
23
|
-
console.log(chalk.yellow("ā Medium risk"));
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
console.log(JSON.stringify(result, null, 2));
|
|
24
18
|
} else {
|
|
25
|
-
console.log(
|
|
19
|
+
console.log(formatPipelineResult(result));
|
|
26
20
|
}
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
console.log(chalk.yellow(`ā ${w}`));
|
|
30
|
-
});
|
|
22
|
+
return result;
|
|
31
23
|
} catch (err) {
|
|
32
|
-
spinner.fail(
|
|
24
|
+
spinner.fail(`Failed to analyze "${pkgName}": ${err.message}`);
|
|
25
|
+
return null;
|
|
33
26
|
}
|
|
34
27
|
}
|
|
@@ -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
|
+
}
|