easy-dep-graph 1.1.3 → 1.2.1

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.
Files changed (4) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +162 -130
  3. package/bin/index.js +2180 -455
  4. package/package.json +74 -67
package/bin/index.js CHANGED
@@ -1,36 +1,1343 @@
1
1
  #! /usr/bin/env node
2
- import shell from "shelljs";
3
- import mustache from "mustache";
4
- import fastify from "fastify";
5
- import open from "open";
6
- import fs from "node:fs";
7
- import path from "node:path";
8
- import semver from "semver";
2
+ import shell from 'shelljs';
3
+ import mustache from 'mustache';
4
+ import fastify from 'fastify';
5
+ import open from 'open';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import semver from 'semver';
9
9
  let flatDeps;
10
+ // ─── Known Malicious / Compromised Packages Database ────────────────
11
+ const knownMaliciousPackages = [
12
+ // === Compromised legitimate packages (specific bad versions) ===
13
+ {
14
+ name: 'axios',
15
+ badVersions: ['1.14.1', '0.30.4'],
16
+ severity: 'critical',
17
+ description: 'Compromised via maintainer account hijack (Mar 2026). Injected RAT malware via plain-crypto-js dependency.',
18
+ },
19
+ {
20
+ name: 'ua-parser-js',
21
+ badVersions: ['0.7.29', '0.8.0', '1.0.0'],
22
+ severity: 'critical',
23
+ description: 'Compromised via account hijack (Oct 2021). Installed crypto miners and Danabot trojan.',
24
+ },
25
+ {
26
+ name: 'coa',
27
+ badVersions: ['2.0.3', '2.0.4', '2.1.1', '2.1.3', '3.1.3'],
28
+ severity: 'critical',
29
+ description: 'Compromised via account hijack (Nov 2021). Malicious preinstall script downloaded Danabot trojan.',
30
+ },
31
+ {
32
+ name: 'rc',
33
+ badVersions: ['1.2.9', '1.3.9', '2.3.9'],
34
+ severity: 'critical',
35
+ description: 'Compromised via account hijack (Nov 2021). Same Danabot trojan payload as coa.',
36
+ },
37
+ {
38
+ name: 'event-stream',
39
+ badVersions: ['3.3.6'],
40
+ severity: 'critical',
41
+ description: 'Compromised via social-engineered maintainer transfer (Nov 2018). Stole Bitcoin from Copay wallets via flatmap-stream.',
42
+ },
43
+ {
44
+ name: 'colors',
45
+ badVersions: ['1.4.1', '1.4.44-liberty-2'],
46
+ severity: 'high',
47
+ description: 'Sabotaged by maintainer (Jan 2022). Infinite loop printing zalgo text causing DoS. Use 1.4.0 instead.',
48
+ },
49
+ {
50
+ name: 'faker',
51
+ badVersions: ['6.6.6'],
52
+ severity: 'high',
53
+ description: 'Sabotaged by maintainer (Jan 2022). Package contents wiped. Use @faker-js/faker instead.',
54
+ },
55
+ {
56
+ name: 'node-ipc',
57
+ badVersions: ['10.1.1', '10.1.2', '9.2.2', '11.0.0', '11.1.0'],
58
+ severity: 'critical',
59
+ description: 'Protestware (Mar 2022). Geo-targeted file destruction for Russian/Belarusian IPs. CVE-2022-23812.',
60
+ },
61
+ {
62
+ name: 'eslint-scope',
63
+ badVersions: ['3.7.2'],
64
+ severity: 'critical',
65
+ description: 'Compromised (Jul 2018). Stole npm tokens from developer machines.',
66
+ },
67
+ // === Always-malicious packages (any version) ===
68
+ {
69
+ name: 'flatmap-stream',
70
+ badVersions: '*',
71
+ severity: 'critical',
72
+ description: 'Malicious package used in event-stream supply chain attack. Stole cryptocurrency credentials.',
73
+ },
74
+ {
75
+ name: 'plain-crypto-js',
76
+ badVersions: '*',
77
+ severity: 'critical',
78
+ description: 'Malicious dependency injected into compromised axios versions. Delivers cross-platform RAT.',
79
+ },
80
+ {
81
+ name: 'peacenotwar',
82
+ badVersions: '*',
83
+ severity: 'high',
84
+ description: 'Protestware dropped by compromised node-ipc. Writes files to user desktop.',
85
+ },
86
+ {
87
+ name: 'getcookies',
88
+ badVersions: '*',
89
+ severity: 'critical',
90
+ description: 'Backdoor hidden in express middleware (May 2018). Remote code execution.',
91
+ },
92
+ {
93
+ name: 'crossenv',
94
+ badVersions: '*',
95
+ severity: 'critical',
96
+ description: 'Typosquat of cross-env. Steals environment variables and npm tokens.',
97
+ },
98
+ {
99
+ name: 'event-strem',
100
+ badVersions: '*',
101
+ severity: 'critical',
102
+ description: 'Typosquat of event-stream. Credential theft.',
103
+ },
104
+ {
105
+ name: 'lodahs',
106
+ badVersions: '*',
107
+ severity: 'critical',
108
+ description: 'Typosquat of lodash. Malware dropper.',
109
+ },
110
+ {
111
+ name: 'babelcli',
112
+ badVersions: '*',
113
+ severity: 'critical',
114
+ description: 'Typosquat of babel-cli. Steals environment variables.',
115
+ },
116
+ {
117
+ name: 'd3.js',
118
+ badVersions: '*',
119
+ severity: 'critical',
120
+ description: 'Typosquat of d3. Credential theft.',
121
+ },
122
+ {
123
+ name: 'ffmpegs',
124
+ badVersions: '*',
125
+ severity: 'critical',
126
+ description: 'Typosquat of ffmpeg. Crypto miner.',
127
+ },
128
+ {
129
+ name: 'gruntcli',
130
+ badVersions: '*',
131
+ severity: 'critical',
132
+ description: 'Typosquat of grunt-cli. Steals environment variables.',
133
+ },
134
+ {
135
+ name: 'http-proxy.js',
136
+ badVersions: '*',
137
+ severity: 'critical',
138
+ description: 'Typosquat of http-proxy. Credential theft.',
139
+ },
140
+ {
141
+ name: 'mongose',
142
+ badVersions: '*',
143
+ severity: 'critical',
144
+ description: 'Typosquat of mongoose. Steals environment variables.',
145
+ },
146
+ {
147
+ name: 'node-fabric',
148
+ badVersions: '*',
149
+ severity: 'critical',
150
+ description: 'Typosquat of fabric. Opens remote shell.',
151
+ },
152
+ {
153
+ name: 'node-opencv',
154
+ badVersions: '*',
155
+ severity: 'critical',
156
+ description: 'Typosquat of opencv. Credential theft.',
157
+ },
158
+ {
159
+ name: 'node-opensl',
160
+ badVersions: '*',
161
+ severity: 'critical',
162
+ description: 'Typosquat of openssl. Credential theft.',
163
+ },
164
+ {
165
+ name: 'node-openssl',
166
+ badVersions: '*',
167
+ severity: 'critical',
168
+ description: 'Typosquat of openssl. Credential theft.',
169
+ },
170
+ {
171
+ name: 'nodecaffe',
172
+ badVersions: '*',
173
+ severity: 'critical',
174
+ description: 'Typosquat of node-caffe. Credential theft.',
175
+ },
176
+ {
177
+ name: 'nodefabric',
178
+ badVersions: '*',
179
+ severity: 'critical',
180
+ description: 'Typosquat of node-fabric. Credential theft.',
181
+ },
182
+ {
183
+ name: 'nodemailer-js',
184
+ badVersions: '*',
185
+ severity: 'critical',
186
+ description: 'Typosquat of nodemailer. Credential theft.',
187
+ },
188
+ {
189
+ name: 'noderequest',
190
+ badVersions: '*',
191
+ severity: 'critical',
192
+ description: 'Typosquat of request. Credential theft.',
193
+ },
194
+ {
195
+ name: 'nodesass',
196
+ badVersions: '*',
197
+ severity: 'critical',
198
+ description: 'Typosquat of node-sass. Credential theft.',
199
+ },
200
+ {
201
+ name: 'nodesqlite',
202
+ badVersions: '*',
203
+ severity: 'critical',
204
+ description: 'Typosquat of sqlite. Credential theft.',
205
+ },
206
+ {
207
+ name: 'shadowsock',
208
+ badVersions: '*',
209
+ severity: 'critical',
210
+ description: 'Typosquat of shadowsocks. Credential theft.',
211
+ },
212
+ {
213
+ name: 'sqlite.js',
214
+ badVersions: '*',
215
+ severity: 'critical',
216
+ description: 'Typosquat of sqlite3. Credential theft.',
217
+ },
218
+ {
219
+ name: 'proxy.js',
220
+ badVersions: '*',
221
+ severity: 'critical',
222
+ description: 'Typosquat of proxy. Credential theft.',
223
+ },
224
+ {
225
+ name: 'discorsd.js',
226
+ badVersions: '*',
227
+ severity: 'critical',
228
+ description: 'Typosquat of discord.js. Token stealer.',
229
+ },
230
+ {
231
+ name: 'colored',
232
+ badVersions: '*',
233
+ severity: 'critical',
234
+ description: 'Typosquat of colors. Credential theft.',
235
+ },
236
+ {
237
+ name: 'mariadb',
238
+ badVersions: '*',
239
+ severity: 'critical',
240
+ description: 'Typosquat of mariasql. Credential theft.',
241
+ },
242
+ {
243
+ name: 'electron-native-notify',
244
+ badVersions: '*',
245
+ severity: 'critical',
246
+ description: 'Malicious package (Jun 2019). Stole cryptocurrency credentials.',
247
+ },
248
+ {
249
+ name: 'rest-client',
250
+ badVersions: '*',
251
+ severity: 'critical',
252
+ description: 'Malicious package (Aug 2019). Injected code to steal credentials.',
253
+ },
254
+ {
255
+ name: 'bootstrap-sass',
256
+ badVersions: '*',
257
+ severity: 'high',
258
+ description: 'Typosquat with crypto miner.',
259
+ },
260
+ // === TanStack ecosystem compromise (May 2026) ===
261
+ // Malware in 42 @tanstack/* packages exfiltrates cloud credentials, GitHub tokens, and SSH keys
262
+ // CVE-2026-45321: https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx
263
+ {
264
+ name: '@tanstack/arktype-adapter',
265
+ badVersions: ['1.166.12', '1.166.15'],
266
+ severity: 'critical',
267
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
268
+ },
269
+ {
270
+ name: '@tanstack/eslint-plugin-router',
271
+ badVersions: ['1.161.9', '1.161.12'],
272
+ severity: 'critical',
273
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
274
+ },
275
+ {
276
+ name: '@tanstack/eslint-plugin-start',
277
+ badVersions: ['0.0.4', '0.0.7'],
278
+ severity: 'critical',
279
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
280
+ },
281
+ {
282
+ name: '@tanstack/history',
283
+ badVersions: ['1.161.9', '1.161.12'],
284
+ severity: 'critical',
285
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
286
+ },
287
+ {
288
+ name: '@tanstack/nitro-v2-vite-plugin',
289
+ badVersions: ['1.154.12', '1.154.15'],
290
+ severity: 'critical',
291
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
292
+ },
293
+ {
294
+ name: '@tanstack/react-router',
295
+ badVersions: ['1.169.5', '1.169.8'],
296
+ severity: 'critical',
297
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
298
+ },
299
+ {
300
+ name: '@tanstack/react-router-devtools',
301
+ badVersions: ['1.166.16', '1.166.19'],
302
+ severity: 'critical',
303
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
304
+ },
305
+ {
306
+ name: '@tanstack/react-router-ssr-query',
307
+ badVersions: ['1.166.15', '1.166.18'],
308
+ severity: 'critical',
309
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
310
+ },
311
+ {
312
+ name: '@tanstack/react-start',
313
+ badVersions: ['1.167.68', '1.167.71'],
314
+ severity: 'critical',
315
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
316
+ },
317
+ {
318
+ name: '@tanstack/react-start-client',
319
+ badVersions: ['1.166.51', '1.166.54'],
320
+ severity: 'critical',
321
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
322
+ },
323
+ {
324
+ name: '@tanstack/react-start-rsc',
325
+ badVersions: ['0.0.47', '0.0.50'],
326
+ severity: 'critical',
327
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
328
+ },
329
+ {
330
+ name: '@tanstack/react-start-server',
331
+ badVersions: ['1.166.55', '1.166.58'],
332
+ severity: 'critical',
333
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
334
+ },
335
+ {
336
+ name: '@tanstack/router-cli',
337
+ badVersions: ['1.166.46', '1.166.49'],
338
+ severity: 'critical',
339
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
340
+ },
341
+ {
342
+ name: '@tanstack/router-core',
343
+ badVersions: ['1.169.5', '1.169.8'],
344
+ severity: 'critical',
345
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
346
+ },
347
+ {
348
+ name: '@tanstack/router-devtools',
349
+ badVersions: ['1.166.16', '1.166.19'],
350
+ severity: 'critical',
351
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
352
+ },
353
+ {
354
+ name: '@tanstack/router-devtools-core',
355
+ badVersions: ['1.167.6', '1.167.9'],
356
+ severity: 'critical',
357
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
358
+ },
359
+ {
360
+ name: '@tanstack/router-generator',
361
+ badVersions: ['1.166.45', '1.166.48'],
362
+ severity: 'critical',
363
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
364
+ },
365
+ {
366
+ name: '@tanstack/router-plugin',
367
+ badVersions: ['1.167.38', '1.167.41'],
368
+ severity: 'critical',
369
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
370
+ },
371
+ {
372
+ name: '@tanstack/router-ssr-query-core',
373
+ badVersions: ['1.168.3', '1.168.6'],
374
+ severity: 'critical',
375
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
376
+ },
377
+ {
378
+ name: '@tanstack/router-utils',
379
+ badVersions: ['1.161.11', '1.161.14'],
380
+ severity: 'critical',
381
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
382
+ },
383
+ {
384
+ name: '@tanstack/router-vite-plugin',
385
+ badVersions: ['1.166.53', '1.166.56'],
386
+ severity: 'critical',
387
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
388
+ },
389
+ {
390
+ name: '@tanstack/solid-router',
391
+ badVersions: ['1.169.5', '1.169.8'],
392
+ severity: 'critical',
393
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
394
+ },
395
+ {
396
+ name: '@tanstack/solid-router-devtools',
397
+ badVersions: ['1.166.16', '1.166.19'],
398
+ severity: 'critical',
399
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
400
+ },
401
+ {
402
+ name: '@tanstack/solid-router-ssr-query',
403
+ badVersions: ['1.166.15', '1.166.18'],
404
+ severity: 'critical',
405
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
406
+ },
407
+ {
408
+ name: '@tanstack/solid-start',
409
+ badVersions: ['1.167.65', '1.167.68'],
410
+ severity: 'critical',
411
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
412
+ },
413
+ {
414
+ name: '@tanstack/solid-start-client',
415
+ badVersions: ['1.166.50', '1.166.53'],
416
+ severity: 'critical',
417
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
418
+ },
419
+ {
420
+ name: '@tanstack/solid-start-server',
421
+ badVersions: ['1.166.54', '1.166.57'],
422
+ severity: 'critical',
423
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
424
+ },
425
+ {
426
+ name: '@tanstack/start-client-core',
427
+ badVersions: ['1.168.5', '1.168.8'],
428
+ severity: 'critical',
429
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
430
+ },
431
+ {
432
+ name: '@tanstack/start-fn-stubs',
433
+ badVersions: ['1.161.9', '1.161.12'],
434
+ severity: 'critical',
435
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
436
+ },
437
+ {
438
+ name: '@tanstack/start-plugin-core',
439
+ badVersions: ['1.169.23', '1.169.26'],
440
+ severity: 'critical',
441
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
442
+ },
443
+ {
444
+ name: '@tanstack/start-server-core',
445
+ badVersions: ['1.167.33', '1.167.36'],
446
+ severity: 'critical',
447
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
448
+ },
449
+ {
450
+ name: '@tanstack/start-static-server-functions',
451
+ badVersions: ['1.166.44', '1.166.47'],
452
+ severity: 'critical',
453
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
454
+ },
455
+ {
456
+ name: '@tanstack/start-storage-context',
457
+ badVersions: ['1.166.38', '1.166.41'],
458
+ severity: 'critical',
459
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
460
+ },
461
+ {
462
+ name: '@tanstack/valibot-adapter',
463
+ badVersions: ['1.166.12', '1.166.15'],
464
+ severity: 'critical',
465
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
466
+ },
467
+ {
468
+ name: '@tanstack/virtual-file-routes',
469
+ badVersions: ['1.161.10', '1.161.13'],
470
+ severity: 'critical',
471
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
472
+ },
473
+ {
474
+ name: '@tanstack/vue-router',
475
+ badVersions: ['1.169.5', '1.169.8'],
476
+ severity: 'critical',
477
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
478
+ },
479
+ {
480
+ name: '@tanstack/vue-router-devtools',
481
+ badVersions: ['1.166.16', '1.166.19'],
482
+ severity: 'critical',
483
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
484
+ },
485
+ {
486
+ name: '@tanstack/vue-router-ssr-query',
487
+ badVersions: ['1.166.15', '1.166.18'],
488
+ severity: 'critical',
489
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
490
+ },
491
+ {
492
+ name: '@tanstack/vue-start',
493
+ badVersions: ['1.167.61', '1.167.64'],
494
+ severity: 'critical',
495
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
496
+ },
497
+ {
498
+ name: '@tanstack/vue-start-client',
499
+ badVersions: ['1.166.46', '1.166.49'],
500
+ severity: 'critical',
501
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
502
+ },
503
+ {
504
+ name: '@tanstack/vue-start-server',
505
+ badVersions: ['1.166.50', '1.166.53'],
506
+ severity: 'critical',
507
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
508
+ },
509
+ {
510
+ name: '@tanstack/zod-adapter',
511
+ badVersions: ['1.166.12', '1.166.15'],
512
+ severity: 'critical',
513
+ description: 'Compromised (May 2026). Malware exfiltrates AWS/GCP credentials, GitHub tokens, SSH keys, and npm tokens. CVE-2026-45321.',
514
+ },
515
+ // === Mistral AI compromise (May 2026) ===
516
+ // Part of "Mini Shai-Hulud is back" worm by TeamPCP threat actor (GHSA-3q49-cfcf-g5fm)
517
+ {
518
+ name: '@mistralai/mistralai',
519
+ badVersions: ['2.2.2', '2.2.3', '2.2.4'],
520
+ severity: 'critical',
521
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm. Steals credentials and propagates to accessed packages. Full system compromise. GHSA-3q49-cfcf-g5fm.',
522
+ },
523
+ {
524
+ name: '@mistralai/mistralai-azure',
525
+ badVersions: ['1.7.1', '1.7.2', '1.7.3'],
526
+ severity: 'critical',
527
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm. Malware exfiltrates credentials and propagates to accessed packages.',
528
+ },
529
+ {
530
+ name: '@mistralai/mistralai-gcp',
531
+ badVersions: ['1.7.1', '1.7.2', '1.7.3'],
532
+ severity: 'critical',
533
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm. Malware exfiltrates credentials and propagates to accessed packages.',
534
+ },
535
+ // === UIPath supply chain compromise (May 2026) ===
536
+ {
537
+ name: '@uipath/packager-tool-functions',
538
+ badVersions: ['0.1.1'],
539
+ severity: 'critical',
540
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
541
+ },
542
+ {
543
+ name: '@uipath/docsai-tool',
544
+ badVersions: ['1.0.1'],
545
+ severity: 'critical',
546
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
547
+ },
548
+ {
549
+ name: '@uipath/context-grounding-tool',
550
+ badVersions: ['0.1.1'],
551
+ severity: 'critical',
552
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
553
+ },
554
+ {
555
+ name: '@uipath/apollo-core',
556
+ badVersions: ['5.9.2'],
557
+ severity: 'critical',
558
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
559
+ },
560
+ {
561
+ name: '@uipath/flow-tool',
562
+ badVersions: ['1.0.2'],
563
+ severity: 'critical',
564
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
565
+ },
566
+ {
567
+ name: '@uipath/maestro-tool',
568
+ badVersions: ['1.0.1'],
569
+ severity: 'critical',
570
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
571
+ },
572
+ {
573
+ name: '@uipath/robot',
574
+ badVersions: ['1.3.4'],
575
+ severity: 'critical',
576
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
577
+ },
578
+ {
579
+ name: '@uipath/integrationservice-tool',
580
+ badVersions: ['1.0.2'],
581
+ severity: 'critical',
582
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
583
+ },
584
+ {
585
+ name: '@uipath/agent-tool',
586
+ badVersions: ['1.0.1'],
587
+ severity: 'critical',
588
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
589
+ },
590
+ {
591
+ name: '@uipath/access-policy-sdk',
592
+ badVersions: ['0.3.1'],
593
+ severity: 'critical',
594
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
595
+ },
596
+ {
597
+ name: '@uipath/rpa-tool',
598
+ badVersions: ['0.9.5'],
599
+ severity: 'critical',
600
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
601
+ },
602
+ {
603
+ name: '@uipath/apollo-wind',
604
+ badVersions: ['2.16.2'],
605
+ severity: 'critical',
606
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
607
+ },
608
+ {
609
+ name: '@uipath/widget.sdk',
610
+ badVersions: ['1.2.3'],
611
+ severity: 'critical',
612
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
613
+ },
614
+ {
615
+ name: '@uipath/common',
616
+ badVersions: ['1.0.1'],
617
+ severity: 'critical',
618
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
619
+ },
620
+ {
621
+ name: '@uipath/functions-tool',
622
+ badVersions: ['1.0.1'],
623
+ severity: 'critical',
624
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
625
+ },
626
+ {
627
+ name: '@uipath/cli',
628
+ badVersions: ['1.0.1'],
629
+ severity: 'critical',
630
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
631
+ },
632
+ {
633
+ name: '@uipath/test-manager-tool',
634
+ badVersions: ['1.0.2'],
635
+ severity: 'critical',
636
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
637
+ },
638
+ {
639
+ name: '@uipath/packager-tool-apiworkflow',
640
+ badVersions: ['0.0.19'],
641
+ severity: 'critical',
642
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
643
+ },
644
+ {
645
+ name: '@uipath/insights-sdk',
646
+ badVersions: ['1.0.1'],
647
+ severity: 'critical',
648
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
649
+ },
650
+ {
651
+ name: '@uipath/rpa-legacy-tool',
652
+ badVersions: ['1.0.1'],
653
+ severity: 'critical',
654
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
655
+ },
656
+ {
657
+ name: '@uipath/solution-packager',
658
+ badVersions: ['0.0.35'],
659
+ severity: 'critical',
660
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
661
+ },
662
+ {
663
+ name: '@uipath/api-workflow-tool',
664
+ badVersions: ['1.0.1'],
665
+ severity: 'critical',
666
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
667
+ },
668
+ {
669
+ name: '@uipath/resources-tool',
670
+ badVersions: ['0.1.11'],
671
+ severity: 'critical',
672
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
673
+ },
674
+ {
675
+ name: '@uipath/ui-widgets-multi-file-upload',
676
+ badVersions: ['1.0.1'],
677
+ severity: 'critical',
678
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
679
+ },
680
+ {
681
+ name: '@uipath/codedagents-tool',
682
+ badVersions: ['0.1.12'],
683
+ severity: 'critical',
684
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
685
+ },
686
+ {
687
+ name: '@uipath/admin-tool',
688
+ badVersions: ['0.1.1'],
689
+ severity: 'critical',
690
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
691
+ },
692
+ {
693
+ name: '@uipath/tool-workflowcompiler',
694
+ badVersions: ['0.0.12'],
695
+ severity: 'critical',
696
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
697
+ },
698
+ {
699
+ name: '@uipath/telemetry',
700
+ badVersions: ['0.0.7'],
701
+ severity: 'critical',
702
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
703
+ },
704
+ {
705
+ name: '@uipath/resourcecatalog-tool',
706
+ badVersions: ['0.1.1'],
707
+ severity: 'critical',
708
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
709
+ },
710
+ {
711
+ name: '@uipath/aops-policy-tool',
712
+ badVersions: ['0.3.1'],
713
+ severity: 'critical',
714
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
715
+ },
716
+ {
717
+ name: '@uipath/identity-tool',
718
+ badVersions: ['0.1.1'],
719
+ severity: 'critical',
720
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
721
+ },
722
+ {
723
+ name: '@uipath/packager-tool-bpmn',
724
+ badVersions: ['0.0.9'],
725
+ severity: 'critical',
726
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
727
+ },
728
+ {
729
+ name: '@uipath/case-tool',
730
+ badVersions: ['1.0.1'],
731
+ severity: 'critical',
732
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
733
+ },
734
+ {
735
+ name: '@uipath/ap-chat',
736
+ badVersions: ['1.5.7'],
737
+ severity: 'critical',
738
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
739
+ },
740
+ {
741
+ name: '@uipath/solutionpackager-sdk',
742
+ badVersions: ['1.0.11'],
743
+ severity: 'critical',
744
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
745
+ },
746
+ {
747
+ name: '@uipath/agent-sdk',
748
+ badVersions: ['1.0.2'],
749
+ severity: 'critical',
750
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
751
+ },
752
+ {
753
+ name: '@uipath/vss',
754
+ badVersions: ['0.1.6'],
755
+ severity: 'critical',
756
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
757
+ },
758
+ {
759
+ name: '@uipath/solution-tool',
760
+ badVersions: ['1.0.1'],
761
+ severity: 'critical',
762
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
763
+ },
764
+ {
765
+ name: '@uipath/maestro-sdk',
766
+ badVersions: ['1.0.1'],
767
+ severity: 'critical',
768
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
769
+ },
770
+ {
771
+ name: '@uipath/packager-tool-workflowcompiler',
772
+ badVersions: ['0.0.16'],
773
+ severity: 'critical',
774
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
775
+ },
776
+ {
777
+ name: '@uipath/data-fabric-tool',
778
+ badVersions: ['1.0.2'],
779
+ severity: 'critical',
780
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
781
+ },
782
+ {
783
+ name: '@uipath/project-packager',
784
+ badVersions: ['1.1.16'],
785
+ severity: 'critical',
786
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
787
+ },
788
+ {
789
+ name: '@uipath/orchestrator-tool',
790
+ badVersions: ['1.0.1'],
791
+ severity: 'critical',
792
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
793
+ },
794
+ {
795
+ name: '@uipath/packager-tool-connector',
796
+ badVersions: ['0.0.19'],
797
+ severity: 'critical',
798
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
799
+ },
800
+ {
801
+ name: '@uipath/tasks-tool',
802
+ badVersions: ['1.0.1'],
803
+ severity: 'critical',
804
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
805
+ },
806
+ {
807
+ name: '@uipath/packager-tool-flow',
808
+ badVersions: ['0.0.19'],
809
+ severity: 'critical',
810
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
811
+ },
812
+ {
813
+ name: '@uipath/integrationservice-sdk',
814
+ badVersions: ['1.0.2'],
815
+ severity: 'critical',
816
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
817
+ },
818
+ {
819
+ name: '@uipath/solutionpackager-tool-core',
820
+ badVersions: ['0.0.34'],
821
+ severity: 'critical',
822
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
823
+ },
824
+ {
825
+ name: '@uipath/vertical-solutions-tool',
826
+ badVersions: ['1.0.1'],
827
+ severity: 'critical',
828
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
829
+ },
830
+ {
831
+ name: '@uipath/insights-tool',
832
+ badVersions: ['1.0.1'],
833
+ severity: 'critical',
834
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
835
+ },
836
+ {
837
+ name: '@uipath/auth',
838
+ badVersions: ['1.0.1'],
839
+ severity: 'critical',
840
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
841
+ },
842
+ {
843
+ name: '@uipath/llmgw-tool',
844
+ badVersions: ['1.0.1'],
845
+ severity: 'critical',
846
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
847
+ },
848
+ {
849
+ name: '@uipath/packager-tool-workflowcompiler-browser',
850
+ badVersions: ['0.0.34'],
851
+ severity: 'critical',
852
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
853
+ },
854
+ {
855
+ name: '@uipath/platform-tool',
856
+ badVersions: ['1.0.1'],
857
+ severity: 'critical',
858
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
859
+ },
860
+ {
861
+ name: '@uipath/codedagent-tool',
862
+ badVersions: ['1.0.1'],
863
+ severity: 'critical',
864
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
865
+ },
866
+ {
867
+ name: '@uipath/codedapp-tool',
868
+ badVersions: ['1.0.1'],
869
+ severity: 'critical',
870
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
871
+ },
872
+ {
873
+ name: '@uipath/resource-tool',
874
+ badVersions: ['1.0.1'],
875
+ severity: 'critical',
876
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
877
+ },
878
+ {
879
+ name: '@uipath/gov-tool',
880
+ badVersions: ['0.3.1'],
881
+ severity: 'critical',
882
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
883
+ },
884
+ {
885
+ name: '@uipath/access-policy-tool',
886
+ badVersions: ['0.3.1'],
887
+ severity: 'critical',
888
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
889
+ },
890
+ {
891
+ name: '@uipath/packager-tool-case',
892
+ badVersions: ['0.0.9'],
893
+ severity: 'critical',
894
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
895
+ },
896
+ {
897
+ name: '@uipath/packager-tool-webapp',
898
+ badVersions: ['1.0.6'],
899
+ severity: 'critical',
900
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
901
+ },
902
+ {
903
+ name: '@uipath/traces-tool',
904
+ badVersions: ['1.0.1'],
905
+ severity: 'critical',
906
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
907
+ },
908
+ {
909
+ name: '@uipath/filesystem',
910
+ badVersions: ['1.0.1'],
911
+ severity: 'critical',
912
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
913
+ },
914
+ {
915
+ name: '@uipath/uipath-python-bridge',
916
+ badVersions: ['1.0.1'],
917
+ severity: 'critical',
918
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
919
+ },
920
+ {
921
+ name: '@uipath/apollo-react',
922
+ badVersions: ['4.24.5'],
923
+ severity: 'critical',
924
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
925
+ },
926
+ {
927
+ name: '@uipath/agent.sdk',
928
+ badVersions: ['0.0.18'],
929
+ severity: 'critical',
930
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
931
+ },
932
+ // === ML Toolkit compromise (May 2026) ===
933
+ {
934
+ name: '@ml-toolkit-ts/xgboost',
935
+ badVersions: ['1.0.3', '1.0.4'],
936
+ severity: 'critical',
937
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
938
+ },
939
+ {
940
+ name: 'ml-toolkit-ts',
941
+ badVersions: ['1.0.4', '1.0.5'],
942
+ severity: 'critical',
943
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
944
+ },
945
+ {
946
+ name: '@ml-toolkit-ts/preprocessing',
947
+ badVersions: ['1.0.2', '1.0.3'],
948
+ severity: 'critical',
949
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
950
+ },
951
+ // === Other ecosystem compromises (May 2026) ===
952
+ {
953
+ name: '@supersurkhet/cli',
954
+ badVersions: ['0.0.2', '0.0.3', '0.0.4', '0.0.5', '0.0.6', '0.0.7'],
955
+ severity: 'critical',
956
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
957
+ },
958
+ {
959
+ name: '@supersurkhet/sdk',
960
+ badVersions: ['0.0.2', '0.0.3', '0.0.4', '0.0.5', '0.0.6', '0.0.7'],
961
+ severity: 'critical',
962
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
963
+ },
964
+ {
965
+ name: '@taskflow-corp/cli',
966
+ badVersions: ['0.1.24', '0.1.25', '0.1.26', '0.1.27', '0.1.28', '0.1.29'],
967
+ severity: 'critical',
968
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
969
+ },
970
+ {
971
+ name: 'safe-action',
972
+ badVersions: ['0.8.3', '0.8.4'],
973
+ severity: 'critical',
974
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
975
+ },
976
+ {
977
+ name: '@draftlab/auth',
978
+ badVersions: ['0.24.1', '0.24.2'],
979
+ severity: 'critical',
980
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
981
+ },
982
+ {
983
+ name: '@draftlab/auth-router',
984
+ badVersions: ['0.5.1', '0.5.2'],
985
+ severity: 'critical',
986
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
987
+ },
988
+ {
989
+ name: '@draftlab/db',
990
+ badVersions: ['0.16.1', '0.16.2'],
991
+ severity: 'critical',
992
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
993
+ },
994
+ {
995
+ name: '@tolka/cli',
996
+ badVersions: ['1.0.2', '1.0.3', '1.0.4', '1.0.5', '1.0.6'],
997
+ severity: 'critical',
998
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
999
+ },
1000
+ {
1001
+ name: '@dirigible-ai/sdk',
1002
+ badVersions: ['0.6.2', '0.6.3'],
1003
+ severity: 'critical',
1004
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1005
+ },
1006
+ {
1007
+ name: '@beproduct/nestjs-auth',
1008
+ badVersions: [
1009
+ '0.1.2',
1010
+ '0.1.3',
1011
+ '0.1.4',
1012
+ '0.1.5',
1013
+ '0.1.6',
1014
+ '0.1.7',
1015
+ '0.1.8',
1016
+ '0.1.9',
1017
+ '0.1.10',
1018
+ '0.1.11',
1019
+ '0.1.12',
1020
+ '0.1.13',
1021
+ '0.1.14',
1022
+ '0.1.15',
1023
+ '0.1.16',
1024
+ '0.1.17',
1025
+ '0.1.18',
1026
+ '0.1.19',
1027
+ ],
1028
+ severity: 'critical',
1029
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1030
+ },
1031
+ {
1032
+ name: 'cmux-agent-mcp',
1033
+ badVersions: ['0.1.3', '0.1.4', '0.1.5', '0.1.6', '0.1.7', '0.1.8'],
1034
+ severity: 'critical',
1035
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1036
+ },
1037
+ {
1038
+ name: 'git-branch-selector',
1039
+ badVersions: ['1.3.3', '1.3.4', '1.3.5', '1.3.6', '1.3.7'],
1040
+ severity: 'critical',
1041
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1042
+ },
1043
+ {
1044
+ name: 'agentwork-cli',
1045
+ badVersions: ['0.1.4', '0.1.5'],
1046
+ severity: 'critical',
1047
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1048
+ },
1049
+ {
1050
+ name: '@draftauth/core',
1051
+ badVersions: ['0.13.1', '0.13.2'],
1052
+ severity: 'critical',
1053
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1054
+ },
1055
+ {
1056
+ name: '@draftauth/client',
1057
+ badVersions: ['0.2.1', '0.2.2'],
1058
+ severity: 'critical',
1059
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1060
+ },
1061
+ {
1062
+ name: 'git-git-git',
1063
+ badVersions: ['1.0.8', '1.0.9', '1.0.10', '1.0.11', '1.0.12'],
1064
+ severity: 'critical',
1065
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1066
+ },
1067
+ {
1068
+ name: 'nextmove-mcp',
1069
+ badVersions: ['0.1.3', '0.1.4', '0.1.5', '0.1.6', '0.1.7'],
1070
+ severity: 'critical',
1071
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1072
+ },
1073
+ {
1074
+ name: 'cross-stitch',
1075
+ badVersions: ['1.1.3', '1.1.4', '1.1.5', '1.1.6', '1.1.7'],
1076
+ severity: 'critical',
1077
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1078
+ },
1079
+ // === Squawk ecosystem compromise (May 2026) ===
1080
+ {
1081
+ name: '@squawk/fix-data',
1082
+ badVersions: ['0.6.4', '0.6.5', '0.6.6', '0.6.7', '0.6.8'],
1083
+ severity: 'critical',
1084
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1085
+ },
1086
+ {
1087
+ name: '@squawk/weather',
1088
+ badVersions: ['0.5.6', '0.5.7', '0.5.8', '0.5.9', '0.5.10'],
1089
+ severity: 'critical',
1090
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1091
+ },
1092
+ {
1093
+ name: '@squawk/icao-registry-data',
1094
+ badVersions: ['0.8.4', '0.8.5', '0.8.6', '0.8.7', '0.8.8'],
1095
+ severity: 'critical',
1096
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1097
+ },
1098
+ {
1099
+ name: '@squawk/airport-data',
1100
+ badVersions: ['0.7.4', '0.7.5', '0.7.6', '0.7.7', '0.7.8'],
1101
+ severity: 'critical',
1102
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1103
+ },
1104
+ {
1105
+ name: '@squawk/flightplan',
1106
+ badVersions: ['0.5.2', '0.5.3', '0.5.4', '0.5.5', '0.5.6'],
1107
+ severity: 'critical',
1108
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1109
+ },
1110
+ {
1111
+ name: '@squawk/units',
1112
+ badVersions: ['0.4.3', '0.4.4', '0.4.5', '0.4.6', '0.4.7'],
1113
+ severity: 'critical',
1114
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1115
+ },
1116
+ {
1117
+ name: '@squawk/flight-math',
1118
+ badVersions: ['0.5.4', '0.5.5', '0.5.6', '0.5.7', '0.5.8'],
1119
+ severity: 'critical',
1120
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1121
+ },
1122
+ {
1123
+ name: '@squawk/fixes',
1124
+ badVersions: ['0.3.2', '0.3.3', '0.3.4', '0.3.5', '0.3.6'],
1125
+ severity: 'critical',
1126
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1127
+ },
1128
+ {
1129
+ name: '@squawk/airspace-data',
1130
+ badVersions: ['0.5.3', '0.5.4', '0.5.5', '0.5.6', '0.5.7'],
1131
+ severity: 'critical',
1132
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1133
+ },
1134
+ {
1135
+ name: '@squawk/procedure-data',
1136
+ badVersions: ['0.7.3', '0.7.4', '0.7.5', '0.7.6', '0.7.7'],
1137
+ severity: 'critical',
1138
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1139
+ },
1140
+ {
1141
+ name: '@squawk/navaids',
1142
+ badVersions: ['0.4.2', '0.4.3', '0.4.4', '0.4.5', '0.4.6'],
1143
+ severity: 'critical',
1144
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1145
+ },
1146
+ {
1147
+ name: '@squawk/notams',
1148
+ badVersions: ['0.3.6', '0.3.7', '0.3.8', '0.3.9', '0.3.10'],
1149
+ severity: 'critical',
1150
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1151
+ },
1152
+ {
1153
+ name: '@squawk/airways',
1154
+ badVersions: ['0.4.2', '0.4.3', '0.4.4', '0.4.5', '0.4.6'],
1155
+ severity: 'critical',
1156
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1157
+ },
1158
+ {
1159
+ name: '@squawk/airports',
1160
+ badVersions: ['0.6.2', '0.6.3', '0.6.4', '0.6.5', '0.6.6'],
1161
+ severity: 'critical',
1162
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1163
+ },
1164
+ {
1165
+ name: '@squawk/icao-registry',
1166
+ badVersions: ['0.5.2', '0.5.3', '0.5.4', '0.5.5', '0.5.6'],
1167
+ severity: 'critical',
1168
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1169
+ },
1170
+ {
1171
+ name: '@squawk/airspace',
1172
+ badVersions: ['0.8.1', '0.8.2', '0.8.3', '0.8.4', '0.8.5'],
1173
+ severity: 'critical',
1174
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1175
+ },
1176
+ {
1177
+ name: '@squawk/geo',
1178
+ badVersions: ['0.4.4', '0.4.5', '0.4.6', '0.4.7', '0.4.8'],
1179
+ severity: 'critical',
1180
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1181
+ },
1182
+ {
1183
+ name: '@squawk/navaid-data',
1184
+ badVersions: ['0.6.4', '0.6.5', '0.6.6', '0.6.7', '0.6.8'],
1185
+ severity: 'critical',
1186
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1187
+ },
1188
+ {
1189
+ name: '@squawk/airway-data',
1190
+ badVersions: ['0.5.4', '0.5.5', '0.5.6', '0.5.7', '0.5.8'],
1191
+ severity: 'critical',
1192
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1193
+ },
1194
+ {
1195
+ name: '@squawk/mcp',
1196
+ badVersions: ['0.9.1', '0.9.2', '0.9.3', '0.9.4', '0.9.5'],
1197
+ severity: 'critical',
1198
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1199
+ },
1200
+ {
1201
+ name: '@squawk/procedures',
1202
+ badVersions: ['0.5.2', '0.5.3', '0.5.4', '0.5.5', '0.5.6'],
1203
+ severity: 'critical',
1204
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1205
+ },
1206
+ {
1207
+ name: '@squawk/types',
1208
+ badVersions: ['0.8.1', '0.8.2', '0.8.3', '0.8.4', '0.8.5'],
1209
+ severity: 'critical',
1210
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1211
+ },
1212
+ // === Additional compromise (May 2026) ===
1213
+ {
1214
+ name: 'ts-dna',
1215
+ badVersions: ['3.0.1', '3.0.2', '3.0.3', '3.0.4', '3.0.5'],
1216
+ severity: 'critical',
1217
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1218
+ },
1219
+ // === TallyUI ecosystem compromise (May 2026) ===
1220
+ {
1221
+ name: '@tallyui/pos',
1222
+ badVersions: ['0.1.1', '0.1.2', '0.1.3'],
1223
+ severity: 'critical',
1224
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1225
+ },
1226
+ {
1227
+ name: '@tallyui/connector-vendure',
1228
+ badVersions: ['1.0.1', '1.0.2', '1.0.3'],
1229
+ severity: 'critical',
1230
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1231
+ },
1232
+ {
1233
+ name: '@tallyui/connector-shopify',
1234
+ badVersions: ['1.0.1', '1.0.2', '1.0.3'],
1235
+ severity: 'critical',
1236
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1237
+ },
1238
+ {
1239
+ name: '@tallyui/components',
1240
+ badVersions: ['1.0.1', '1.0.2', '1.0.3'],
1241
+ severity: 'critical',
1242
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1243
+ },
1244
+ {
1245
+ name: '@tallyui/theme',
1246
+ badVersions: ['0.2.1', '0.2.2', '0.2.3'],
1247
+ severity: 'critical',
1248
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1249
+ },
1250
+ {
1251
+ name: 'wot-api',
1252
+ badVersions: ['0.8.1', '0.8.2', '0.8.3', '0.8.4'],
1253
+ severity: 'critical',
1254
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1255
+ },
1256
+ {
1257
+ name: '@tallyui/storage-sqlite',
1258
+ badVersions: ['0.2.1', '0.2.2', '0.2.3'],
1259
+ severity: 'critical',
1260
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1261
+ },
1262
+ {
1263
+ name: '@tallyui/connector-woocommerce',
1264
+ badVersions: ['1.0.1', '1.0.2', '1.0.3'],
1265
+ severity: 'critical',
1266
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1267
+ },
1268
+ {
1269
+ name: '@tallyui/database',
1270
+ badVersions: ['1.0.1', '1.0.2', '1.0.3'],
1271
+ severity: 'critical',
1272
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1273
+ },
1274
+ {
1275
+ name: '@tallyui/connector-medusa',
1276
+ badVersions: ['1.0.1', '1.0.2', '1.0.3'],
1277
+ severity: 'critical',
1278
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1279
+ },
1280
+ {
1281
+ name: '@tallyui/core',
1282
+ badVersions: ['0.2.1', '0.2.2', '0.2.3'],
1283
+ severity: 'critical',
1284
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1285
+ },
1286
+ // === Mesa/MesaDev compromise (May 2026) ===
1287
+ {
1288
+ name: '@mesadev/saguaro',
1289
+ badVersions: ['0.4.22'],
1290
+ severity: 'critical',
1291
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1292
+ },
1293
+ {
1294
+ name: '@mesadev/sdk',
1295
+ badVersions: ['0.28.3'],
1296
+ severity: 'critical',
1297
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1298
+ },
1299
+ {
1300
+ name: '@mesadev/rest',
1301
+ badVersions: ['0.28.3'],
1302
+ severity: 'critical',
1303
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1304
+ },
1305
+ // === OpenSearch compromise (May 2026) ===
1306
+ {
1307
+ name: '@opensearch-project/opensearch',
1308
+ badVersions: ['3.5.3', '3.6.2', '3.7.0', '3.8.0'],
1309
+ severity: 'critical',
1310
+ description: 'Compromised (May 2026). Part of "Mini Shai-Hulud is back" worm.',
1311
+ },
1312
+ ];
10
1313
  void (async function main() {
11
1314
  // Check if peer dependencies mode
12
- const peerDependenciesMode = process.argv.findIndex((a) => a.toLowerCase() === "--peer-dependencies") !==
13
- -1;
1315
+ const peerDependenciesMode = process.argv.findIndex((a) => a.toLowerCase() === '--peer-dependencies') !== -1;
14
1316
  // List of packages user wants to see the dependencies
15
- const packagesArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--packages");
1317
+ const packagesArgIndex = process.argv.findIndex((a) => a.toLowerCase() === '--packages');
16
1318
  const packagesFilter = packagesArgIndex !== -1 ? process.argv[packagesArgIndex + 1] : undefined;
17
1319
  // Only get the dependents of a specific package
18
- const packageArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--package-dependents");
1320
+ const packageArgIndex = process.argv.findIndex((a) => a.toLowerCase() === '--package-dependents');
19
1321
  const packageName = packageArgIndex !== -1 ? process.argv[packageArgIndex + 1] : undefined;
20
1322
  // Get port number
21
- const portArgIndex = process.argv.findIndex((a) => a.toLowerCase() === "--port");
1323
+ const portArgIndex = process.argv.findIndex((a) => a.toLowerCase() === '--port');
22
1324
  const port = portArgIndex !== -1 ? +process.argv[portArgIndex + 1] : 8080;
23
1325
  // Get if should not open browser
24
- const shouldOpenBrowser = process.argv.findIndex((a) => a.toLowerCase() === "--no-open") === -1;
1326
+ const shouldOpenBrowser = process.argv.findIndex((a) => a.toLowerCase() === '--no-open') === -1;
25
1327
  // Get if should not apply force layout
26
- const shouldApplyForceLayout = process.argv.findIndex((a) => a.toLowerCase() === "--no-force-layout") ===
27
- -1;
1328
+ const shouldApplyForceLayout = process.argv.findIndex((a) => a.toLowerCase() === '--no-force-layout') === -1;
1329
+ // Check if security scan mode
1330
+ const securityScanMode = process.argv.findIndex((a) => a.toLowerCase() === '--security-scan') !== -1;
1331
+ if (securityScanMode) {
1332
+ await runSecurityScanMode(port, shouldOpenBrowser);
1333
+ return;
1334
+ }
28
1335
  if (peerDependenciesMode) {
29
1336
  await runPeerDependenciesMode(port, shouldOpenBrowser);
30
1337
  return;
31
1338
  }
32
1339
  // Run npm list command
33
- const result = shell.exec(`npm list --json ${packageName ?? "--all"}`, {
1340
+ const result = shell.exec(`npm list --json ${packageName ?? '--all'}`, {
34
1341
  windowsHide: true,
35
1342
  silent: true,
36
1343
  });
@@ -39,7 +1346,7 @@ void (async function main() {
39
1346
  console.log(`Generating dependency graph for: "${packageInfo.name}"...`);
40
1347
  // Filter dependencies if needed
41
1348
  if (packagesFilter != null) {
42
- const packagesFilterList = packagesFilter.split(",").map((d) => d.trim());
1349
+ const packagesFilterList = packagesFilter.split(',').map((d) => d.trim());
43
1350
  for (const key of Object.keys(packageInfo.dependencies)) {
44
1351
  if (!packagesFilterList.includes(key)) {
45
1352
  delete packageInfo.dependencies[key];
@@ -75,7 +1382,7 @@ void (async function main() {
75
1382
  x,
76
1383
  y,
77
1384
  size: 10,
78
- color: dep[1].isRoot ? "#22c55e" : "#3b82f6",
1385
+ color: dep[1].isRoot ? '#22c55e' : '#3b82f6',
79
1386
  };
80
1387
  })),
81
1388
  edges: JSON.stringify(Object.entries(flatDeps)
@@ -91,7 +1398,7 @@ void (async function main() {
91
1398
  const app = fastify({
92
1399
  logger: false,
93
1400
  });
94
- app.get("/", (_req, resp) => resp.type("text/html").send(html));
1401
+ app.get('/', (_req, resp) => resp.type('text/html').send(html));
95
1402
  // Run the server
96
1403
  app.listen({ port }, (err, address) => {
97
1404
  if (err)
@@ -123,10 +1430,10 @@ function flatDepsRecursive(deps, parentDepName) {
123
1430
  }
124
1431
  }
125
1432
  async function runPeerDependenciesMode(port, shouldOpenBrowser) {
126
- console.log("Analyzing peer dependencies...");
1433
+ console.log('Analyzing peer dependencies...');
127
1434
  // Get the current project's package.json
128
- const packageJsonPath = path.join(process.cwd(), "package.json");
129
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
1435
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
1436
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
130
1437
  const installedPackages = {
131
1438
  ...packageJson.dependencies,
132
1439
  ...packageJson.devDependencies,
@@ -138,8 +1445,8 @@ async function runPeerDependenciesMode(port, shouldOpenBrowser) {
138
1445
  const resolvedVersion = resolveVersion(info.versions);
139
1446
  const isInPackageJson = installedPackages[name] !== undefined;
140
1447
  // Check if installed in node_modules (even if not in package.json)
141
- const nodeModulesPath = path.join(process.cwd(), "node_modules");
142
- const pkgPath = path.join(nodeModulesPath, name, "package.json");
1448
+ const nodeModulesPath = path.join(process.cwd(), 'node_modules');
1449
+ const pkgPath = path.join(nodeModulesPath, name, 'package.json');
143
1450
  const existsInNodeModules = fs.existsSync(pkgPath);
144
1451
  const isInstalledByDependency = existsInNodeModules && !isInPackageJson;
145
1452
  return {
@@ -163,8 +1470,8 @@ async function runPeerDependenciesMode(port, shouldOpenBrowser) {
163
1470
  const app = fastify({
164
1471
  logger: false,
165
1472
  });
166
- app.get("/", (_req, resp) => resp.type("text/html").send(html));
167
- app.post("/install", async (req, resp) => {
1473
+ app.get('/', (_req, resp) => resp.type('text/html').send(html));
1474
+ app.post('/install', async (req, resp) => {
168
1475
  const { package: pkg, version } = req.body;
169
1476
  console.log(`Installing ${pkg}@${version}...`);
170
1477
  const installResult = shell.exec(`npm install ${pkg}@${version}`, {
@@ -182,7 +1489,7 @@ async function runPeerDependenciesMode(port, shouldOpenBrowser) {
182
1489
  console.error(`Failed to install ${pkg}@${version}`);
183
1490
  return {
184
1491
  success: false,
185
- message: installResult.stderr || "Installation failed",
1492
+ message: installResult.stderr || 'Installation failed',
186
1493
  };
187
1494
  }
188
1495
  });
@@ -197,7 +1504,7 @@ async function runPeerDependenciesMode(port, shouldOpenBrowser) {
197
1504
  }
198
1505
  function collectPeerDependenciesRecursively(initialPackages) {
199
1506
  const peerDeps = {};
200
- const nodeModulesPath = path.join(process.cwd(), "node_modules");
1507
+ const nodeModulesPath = path.join(process.cwd(), 'node_modules');
201
1508
  const visited = new Set();
202
1509
  const queue = Object.keys(initialPackages);
203
1510
  while (queue.length > 0) {
@@ -208,11 +1515,11 @@ function collectPeerDependenciesRecursively(initialPackages) {
208
1515
  }
209
1516
  visited.add(packageName);
210
1517
  try {
211
- const pkgJsonPath = path.join(nodeModulesPath, packageName, "package.json");
1518
+ const pkgJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
212
1519
  if (!fs.existsSync(pkgJsonPath)) {
213
1520
  continue;
214
1521
  }
215
- const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
1522
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
216
1523
  // Collect peer dependencies
217
1524
  if (pkgJson.peerDependencies) {
218
1525
  for (const [peerDepName, peerDepVersion] of Object.entries(pkgJson.peerDependencies)) {
@@ -254,7 +1561,7 @@ function collectPeerDependenciesRecursively(initialPackages) {
254
1561
  }
255
1562
  function resolveVersion(versions) {
256
1563
  if (versions.length === 0) {
257
- return { version: "*", isConflict: false };
1564
+ return { version: '*', isConflict: false };
258
1565
  }
259
1566
  if (versions.length === 1) {
260
1567
  return { version: versions[0], isConflict: false };
@@ -286,445 +1593,863 @@ function resolveVersion(versions) {
286
1593
  }
287
1594
  else {
288
1595
  // Ranges don't intersect - conflict detected
289
- return { version: uniqueVersions.join(" | "), isConflict: true };
1596
+ return { version: uniqueVersions.join(' | '), isConflict: true };
290
1597
  }
291
1598
  }
292
1599
  return { version: intersection, isConflict: false };
293
1600
  }
294
1601
  catch (error) {
295
1602
  // If semver parsing fails, fall back to showing all versions
296
- return { version: uniqueVersions.join(" | "), isConflict: true };
1603
+ return { version: uniqueVersions.join(' | '), isConflict: true };
297
1604
  }
298
1605
  }
299
- function getPeerDepsTemplate() {
300
- return `
301
- <html>
302
- <head>
303
- <title>{{projectName}} - Peer Dependencies</title>
304
- <style>
305
- * {
306
- margin: 0;
307
- padding: 0;
308
- box-sizing: border-box;
309
- }
310
-
311
- body {
312
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
313
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
314
- min-height: 100vh;
315
- padding: 2rem;
316
- }
317
-
318
- .container {
319
- max-width: 1200px;
320
- margin: 0 auto;
321
- }
322
-
323
- h1 {
324
- color: white;
325
- margin-bottom: 2rem;
326
- font-size: 2.5rem;
327
- text-align: center;
328
- text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
329
- }
330
-
331
- .peer-deps-list {
332
- background: white;
333
- border-radius: 12px;
334
- box-shadow: 0 10px 40px rgba(0,0,0,0.1);
335
- overflow: hidden;
336
- }
337
-
338
- .peer-dep-item {
339
- padding: 1.5rem;
340
- border-bottom: 1px solid #e5e7eb;
341
- transition: background-color 0.2s;
342
- }
343
-
344
- .peer-dep-item:last-child {
345
- border-bottom: none;
346
- }
347
-
348
- .peer-dep-item:hover {
349
- background-color: #f9fafb;
350
- }
351
-
352
- .peer-dep-header {
353
- display: flex;
354
- justify-content: space-between;
355
- align-items: center;
356
- margin-bottom: 0.75rem;
357
- }
358
-
359
- .peer-dep-name {
360
- font-size: 1.25rem;
361
- font-weight: 600;
362
- color: #1f2937;
363
- }
364
-
365
- .peer-dep-version {
366
- font-family: 'Courier New', monospace;
367
- padding: 0.25rem 0.75rem;
368
- background: #f3f4f6;
369
- border-radius: 6px;
370
- font-size: 0.875rem;
371
- color: #4b5563;
372
- }
373
-
374
- .peer-dep-version.conflict {
375
- background: #fee2e2;
376
- color: #dc2626;
377
- font-weight: 600;
378
- }
379
-
380
- .peer-dep-info {
381
- display: flex;
382
- justify-content: space-between;
383
- align-items: center;
384
- flex-wrap: wrap;
385
- gap: 1rem;
386
- }
387
-
388
- .required-by {
389
- flex: 1;
390
- min-width: 200px;
391
- }
392
-
393
- .required-by-label {
394
- font-size: 0.75rem;
395
- color: #6b7280;
396
- text-transform: uppercase;
397
- letter-spacing: 0.05em;
398
- margin-bottom: 0.25rem;
399
- }
400
-
401
- .required-by-list {
402
- display: flex;
403
- flex-wrap: wrap;
404
- gap: 0.5rem;
405
- }
406
-
407
- .required-by-tag {
408
- display: inline-block;
409
- padding: 0.25rem 0.5rem;
410
- background: #dbeafe;
411
- color: #1e40af;
412
- border-radius: 4px;
413
- font-size: 0.75rem;
414
- font-weight: 500;
415
- }
416
-
417
- .install-btn {
418
- padding: 0.5rem 1.5rem;
419
- background: #3b82f6;
420
- color: white;
421
- border: none;
422
- border-radius: 6px;
423
- font-size: 0.875rem;
424
- font-weight: 500;
425
- cursor: pointer;
426
- transition: all 0.2s;
427
- box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
428
- }
429
-
430
- .install-btn:hover {
431
- background: #2563eb;
432
- transform: translateY(-1px);
433
- box-shadow: 0 4px 8px rgba(59, 130, 246, 0.4);
434
- }
435
-
436
- .install-btn:active {
437
- transform: translateY(0);
438
- }
439
-
440
- .install-btn:disabled {
441
- background: #9ca3af;
442
- cursor: not-allowed;
443
- transform: none;
444
- box-shadow: none;
445
- }
446
-
447
- .installed-badge {
448
- display: flex;
449
- align-items: center;
450
- gap: 0.5rem;
451
- padding: 0.5rem 1rem;
452
- background: #d1fae5;
453
- color: #065f46;
454
- border-radius: 6px;
455
- font-size: 0.875rem;
456
- font-weight: 600;
457
- }
458
-
459
- .installed-by-dep-badge {
460
- display: flex;
461
- align-items: center;
462
- gap: 0.5rem;
463
- padding: 0.5rem 1rem;
464
- background: #e0e7ff;
465
- color: #3730a3;
466
- border-radius: 6px;
467
- font-size: 0.875rem;
468
- font-weight: 600;
469
- }
470
-
471
- .checkmark {
472
- color: #10b981;
473
- font-size: 1.25rem;
474
- }
475
-
476
- .message {
477
- margin-top: 0.5rem;
478
- padding: 0.5rem;
479
- border-radius: 4px;
480
- font-size: 0.875rem;
481
- display: none;
482
- }
483
-
484
- .message.success {
485
- background: #d1fae5;
486
- color: #065f46;
487
- display: block;
488
- }
489
-
490
- .message.error {
491
- background: #fee2e2;
492
- color: #dc2626;
493
- display: block;
494
- }
495
-
496
- .no-deps {
497
- padding: 3rem;
498
- text-align: center;
499
- color: #6b7280;
500
- font-size: 1.125rem;
501
- }
502
- </style>
503
- </head>
504
- <body>
505
- <div class="container">
506
- <h1>Peer Dependencies for {{projectName}}</h1>
507
- <div class="peer-deps-list">
508
- {{#peerDeps}}
509
- <div class="peer-dep-item">
510
- <div class="peer-dep-header">
511
- <span class="peer-dep-name">{{name}}</span>
512
- <span class="peer-dep-version {{#isConflict}}conflict{{/isConflict}}">
513
- {{#isConflict}}Conflict: {{/isConflict}}{{version}}
514
- </span>
515
- </div>
516
- <div class="peer-dep-info">
517
- <div class="required-by">
518
- <div class="required-by-label">Required by:</div>
519
- <div class="required-by-list">
520
- {{#requiredBy}}
521
- <span class="required-by-tag">{{.}}</span>
522
- {{/requiredBy}}
523
- </div>
524
- </div>
525
- <div class="action-container">
526
- {{#isInstalled}}
527
- <div class="installed-badge">
528
- Installed
529
- </div>
530
- {{/isInstalled}}
531
- {{#isInstalledByDependency}}
532
- <div class="installed-by-dep-badge">
533
- Installed by dependency
534
- </div>
535
- {{/isInstalledByDependency}}
536
- {{^isInstalled}}
537
- {{^isInstalledByDependency}}
538
- <button class="install-btn" onclick="installPackage('{{name}}', '{{version}}', this)">
539
- npm install
540
- </button>
541
- <div class="message" id="msg-{{name}}"></div>
542
- {{/isInstalledByDependency}}
543
- {{/isInstalled}}
544
- </div>
545
- </div>
546
- </div>
547
- {{/peerDeps}}
548
- {{^peerDeps}}
549
- <div class="no-deps">
550
- No peer dependencies found!
551
- </div>
552
- {{/peerDeps}}
553
- </div>
554
- </div>
555
-
556
- <script>
557
- async function installPackage(packageName, version, button) {
558
- const messageEl = document.getElementById('msg-' + packageName);
559
-
560
- button.disabled = true;
561
- button.textContent = 'Installing...';
562
- messageEl.className = 'message';
563
- messageEl.textContent = '';
564
-
565
- try {
566
- const response = await fetch('/install', {
567
- method: 'POST',
568
- headers: {
569
- 'Content-Type': 'application/json',
570
- },
571
- body: JSON.stringify({
572
- package: packageName,
573
- version: version
574
- })
575
- });
576
-
577
- const result = await response.json();
578
-
579
- if (result.success) {
580
- messageEl.className = 'message success';
581
- messageEl.textContent = result.message;
582
- button.style.display = 'none';
583
-
584
- // Optionally reload after a delay
585
- setTimeout(() => {
586
- location.reload();
587
- }, 2000);
588
- } else {
589
- messageEl.className = 'message error';
590
- messageEl.textContent = result.message;
591
- button.disabled = false;
592
- button.textContent = 'npm install';
593
- }
594
- } catch (error) {
595
- messageEl.className = 'message error';
596
- messageEl.textContent = 'Failed to install package: ' + error.message;
597
- button.disabled = false;
598
- button.textContent = 'npm install';
599
- }
600
- }
601
- </script>
602
- </body>
603
- </html>
604
- `;
605
- }
606
- function getTemplate() {
607
- return `
608
- <html>
609
- <head>
610
- <title>{{name}}'s Dependency Graph</title>
611
- <script src="https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js"></script>
612
- <script src="https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js"></script>
613
- <script type="text/javascript">
614
- function applyForceLayout(graph, iterations) {
615
- var nodes = graph.nodes();
616
- var edges = graph.edges();
617
-
618
- // Physics constants - lower repulsion for better spacing
619
- var repulsionStrength = 50;
620
- var attractionStrength = 0.01;
621
- var damping = 0.5;
622
-
623
- for (var iter = 0; iter < iterations; iter++) {
624
- var forces = {};
625
-
626
- // Initialize forces
627
- nodes.forEach(function(nodeId) {
628
- forces[nodeId] = { x: 0, y: 0 };
629
- });
630
-
631
- // Repulsive forces between all nodes
632
- for (var i = 0; i < nodes.length; i++) {
633
- for (var j = i + 1; j < nodes.length; j++) {
634
- var node1 = nodes[i];
635
- var node2 = nodes[j];
636
- var attrs1 = graph.getNodeAttributes(node1);
637
- var attrs2 = graph.getNodeAttributes(node2);
638
-
639
- var dx = attrs2.x - attrs1.x;
640
- var dy = attrs2.y - attrs1.y;
641
- var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
642
- var force = repulsionStrength / (distance * distance);
643
-
644
- var fx = (dx / distance) * force;
645
- var fy = (dy / distance) * force;
646
-
647
- forces[node1].x -= fx;
648
- forces[node1].y -= fy;
649
- forces[node2].x += fx;
650
- forces[node2].y += fy;
1606
+ // ─── Security Scan Logic ────────────────────────────────────────────
1607
+ async function runSecurityScanMode(port, shouldOpenBrowser) {
1608
+ console.log('Running security scan...\n');
1609
+ const findings = [];
1610
+ const nodeModulesPath = path.join(process.cwd(), 'node_modules');
1611
+ // Phase 1: Scan node_modules against known malicious packages
1612
+ if (fs.existsSync(nodeModulesPath)) {
1613
+ const entries = fs.readdirSync(nodeModulesPath);
1614
+ for (const entry of entries) {
1615
+ // Handle scoped packages (@scope/name)
1616
+ if (entry.startsWith('@')) {
1617
+ const scopePath = path.join(nodeModulesPath, entry);
1618
+ try {
1619
+ const scopedEntries = fs.readdirSync(scopePath);
1620
+ for (const scopedEntry of scopedEntries) {
1621
+ const fullName = `${entry}/${scopedEntry}`;
1622
+ checkPackageAgainstDatabase(nodeModulesPath, fullName, findings);
651
1623
  }
652
1624
  }
653
-
654
- // Attractive forces along edges
655
- edges.forEach(function(edgeId) {
656
- var edge = graph.extremities(edgeId);
657
- var source = edge.source || edge[0];
658
- var target = edge.target || edge[1];
659
- var attrs1 = graph.getNodeAttributes(source);
660
- var attrs2 = graph.getNodeAttributes(target);
661
-
662
- var dx = attrs2.x - attrs1.x;
663
- var dy = attrs2.y - attrs1.y;
664
- var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
665
-
666
- var fx = dx * attractionStrength;
667
- var fy = dy * attractionStrength;
668
-
669
- forces[source].x += fx;
670
- forces[source].y += fy;
671
- forces[target].x -= fx;
672
- forces[target].y -= fy;
673
- });
674
-
675
- // Apply forces with damping
676
- nodes.forEach(function(nodeId) {
677
- var attrs = graph.getNodeAttributes(nodeId);
678
- attrs.x += forces[nodeId].x * damping;
679
- attrs.y += forces[nodeId].y * damping;
680
- });
1625
+ catch {
1626
+ // Skip unreadable scope directories
1627
+ }
1628
+ continue;
681
1629
  }
1630
+ checkPackageAgainstDatabase(nodeModulesPath, entry, findings);
682
1631
  }
683
-
684
- function renderGraph() {
685
- var nodes = {{{nodes}}};
686
- var edges = {{{edges}}};
687
-
688
- var graph = new graphology.Graph();
689
-
690
- nodes.forEach(function(node) {
691
- graph.addNode(node.key, {
692
- label: node.label,
693
- x: node.x,
694
- y: node.y,
695
- size: node.size,
696
- color: node.color
697
- });
698
- });
699
-
700
- edges.forEach(function(edge, index) {
701
- graph.mergeEdge(edge.source, edge.target, {
702
- size: 2,
703
- color: '#94a3b8'
1632
+ }
1633
+ else {
1634
+ console.warn('Warning: node_modules directory not found. Skipping local package scan.\n');
1635
+ }
1636
+ // Phase 2: Run npm audit
1637
+ console.log('Running npm audit...');
1638
+ const auditResult = shell.exec('npm audit --json', {
1639
+ windowsHide: true,
1640
+ silent: true,
1641
+ });
1642
+ if (auditResult.code !== 0 && auditResult.stdout.trim() === '') {
1643
+ console.warn('Warning: npm audit failed (missing package-lock.json or no network). Showing known-malicious results only.\n');
1644
+ }
1645
+ else {
1646
+ try {
1647
+ const auditData = JSON.parse(auditResult.stdout);
1648
+ const vulnerabilities = auditData.vulnerabilities ?? {};
1649
+ for (const [pkgName, vulnInfo] of Object.entries(vulnerabilities)) {
1650
+ // Skip if already flagged by our known-malicious list
1651
+ if (findings.some((f) => f.name === pkgName))
1652
+ continue;
1653
+ const severity = vulnInfo.severity ?? 'moderate';
1654
+ const via = Array.isArray(vulnInfo.via)
1655
+ ? vulnInfo.via
1656
+ .map((v) => (typeof v === 'string' ? v : (v.title ?? v.name ?? '')))
1657
+ .filter(Boolean)
1658
+ .join('; ')
1659
+ : String(vulnInfo.via ?? '');
1660
+ const installedVer = vulnInfo.range ?? vulnInfo.version ?? 'unknown';
1661
+ findings.push({
1662
+ name: pkgName,
1663
+ installedVersion: installedVer,
1664
+ severity: severity,
1665
+ source: 'npm-audit',
1666
+ description: via || `Vulnerability reported by npm audit (${severity})`,
704
1667
  });
1668
+ }
1669
+ }
1670
+ catch {
1671
+ console.warn('Warning: Could not parse npm audit output.\n');
1672
+ }
1673
+ }
1674
+ // Sort findings: critical first, then high, moderate, low
1675
+ const severityOrder = { critical: 0, high: 1, moderate: 2, low: 3 };
1676
+ findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
1677
+ // Phase 3: Terminal output
1678
+ printSecurityScanResults(findings);
1679
+ // Phase 4: Serve HTML report
1680
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
1681
+ let projectName = 'Unknown Project';
1682
+ try {
1683
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
1684
+ projectName = packageJson.name ?? projectName;
1685
+ }
1686
+ catch {
1687
+ // Ignore
1688
+ }
1689
+ const criticalCount = findings.filter((f) => f.severity === 'critical').length;
1690
+ const highCount = findings.filter((f) => f.severity === 'high').length;
1691
+ const moderateCount = findings.filter((f) => f.severity === 'moderate').length;
1692
+ const lowCount = findings.filter((f) => f.severity === 'low').length;
1693
+ const data = {
1694
+ projectName,
1695
+ findings,
1696
+ totalCount: findings.length,
1697
+ criticalCount,
1698
+ highCount,
1699
+ moderateCount,
1700
+ lowCount,
1701
+ hasCritical: criticalCount > 0,
1702
+ hasHigh: highCount > 0,
1703
+ hasModerate: moderateCount > 0,
1704
+ hasLow: lowCount > 0,
1705
+ hasFindings: findings.length > 0,
1706
+ };
1707
+ const html = mustache.render(getSecurityScanTemplate(), data);
1708
+ const app = fastify({ logger: false });
1709
+ app.get('/', (_req, resp) => resp.type('text/html').send(html));
1710
+ app.listen({ port }, (err, address) => {
1711
+ if (err)
1712
+ throw err;
1713
+ console.log(`\nSecurity report available at: ${address}`);
1714
+ if (shouldOpenBrowser)
1715
+ open(address);
1716
+ });
1717
+ }
1718
+ function checkPackageAgainstDatabase(nodeModulesPath, packageName, findings) {
1719
+ const pkgJsonPath = path.join(nodeModulesPath, packageName, 'package.json');
1720
+ if (!fs.existsSync(pkgJsonPath))
1721
+ return;
1722
+ let installedVersion;
1723
+ try {
1724
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
1725
+ installedVersion = pkgJson.version ?? '0.0.0';
1726
+ }
1727
+ catch {
1728
+ return;
1729
+ }
1730
+ for (const entry of knownMaliciousPackages) {
1731
+ if (entry.name !== packageName)
1732
+ continue;
1733
+ if (entry.badVersions === '*') {
1734
+ findings.push({
1735
+ name: packageName,
1736
+ installedVersion,
1737
+ severity: entry.severity,
1738
+ source: 'known-malicious',
1739
+ description: entry.description,
705
1740
  });
706
-
707
- // Apply custom force-directed layout
708
- {{#applyForceLayout}}
709
- applyForceLayout(graph, 100);
710
- {{/applyForceLayout}}
711
-
712
- var container = document.getElementById("dep-graph");
713
- var renderer = new Sigma(graph, container, {
714
- renderEdgeLabels: false,
715
- defaultNodeColor: '#3b82f6',
716
- defaultEdgeColor: '#94a3b8'
1741
+ }
1742
+ else if (entry.badVersions.includes(installedVersion)) {
1743
+ findings.push({
1744
+ name: packageName,
1745
+ installedVersion,
1746
+ severity: entry.severity,
1747
+ source: 'known-malicious',
1748
+ description: entry.description,
717
1749
  });
718
1750
  }
719
- </script>
720
- <style>
721
- body { margin: 0; padding: 0; }
722
- #dep-graph { width: 100vw; height: 100vh; background: #f8fafc; }
723
- </style>
724
- </head>
725
- <body onload="renderGraph()">
726
- <div id="dep-graph"></div>
727
- </body>
728
- </html>
1751
+ }
1752
+ }
1753
+ function printSecurityScanResults(findings) {
1754
+ if (findings.length === 0) {
1755
+ console.log('\x1b[32m✔ No known malicious packages or vulnerabilities found.\x1b[0m\n');
1756
+ return;
1757
+ }
1758
+ const critical = findings.filter((f) => f.severity === 'critical');
1759
+ const high = findings.filter((f) => f.severity === 'high');
1760
+ const moderate = findings.filter((f) => f.severity === 'moderate');
1761
+ const low = findings.filter((f) => f.severity === 'low');
1762
+ console.log(`\x1b[1mSecurity Scan Results:\x1b[0m`);
1763
+ console.log(`─────────────────────────────────────────────`);
1764
+ if (critical.length > 0)
1765
+ console.log(` \x1b[31m● ${critical.length} critical\x1b[0m`);
1766
+ if (high.length > 0)
1767
+ console.log(` \x1b[33m● ${high.length} high\x1b[0m`);
1768
+ if (moderate.length > 0)
1769
+ console.log(` \x1b[36m● ${moderate.length} moderate\x1b[0m`);
1770
+ if (low.length > 0)
1771
+ console.log(` \x1b[37m● ${low.length} low\x1b[0m`);
1772
+ console.log(`─────────────────────────────────────────────\n`);
1773
+ for (const finding of findings) {
1774
+ const color = finding.severity === 'critical'
1775
+ ? '\x1b[31m'
1776
+ : finding.severity === 'high'
1777
+ ? '\x1b[33m'
1778
+ : finding.severity === 'moderate'
1779
+ ? '\x1b[36m'
1780
+ : '\x1b[37m';
1781
+ const sourceTag = finding.source === 'known-malicious' ? '[KNOWN MALICIOUS]' : '[npm audit]';
1782
+ console.log(` ${color}${finding.severity.toUpperCase()}\x1b[0m ${finding.name}@${finding.installedVersion} ${sourceTag}`);
1783
+ console.log(` ${finding.description}`);
1784
+ console.log();
1785
+ }
1786
+ }
1787
+ // ─── Security Scan HTML Template ────────────────────────────────────
1788
+ function getSecurityScanTemplate() {
1789
+ return `
1790
+ <html>
1791
+ <head>
1792
+ <title>{{projectName}} - Security Scan</title>
1793
+ <style>
1794
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1795
+
1796
+ body {
1797
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
1798
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
1799
+ min-height: 100vh;
1800
+ padding: 2rem;
1801
+ color: #e2e8f0;
1802
+ }
1803
+
1804
+ .container { max-width: 1200px; margin: 0 auto; }
1805
+
1806
+ h1 {
1807
+ color: white;
1808
+ margin-bottom: 0.5rem;
1809
+ font-size: 2.5rem;
1810
+ text-align: center;
1811
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
1812
+ }
1813
+
1814
+ .subtitle {
1815
+ text-align: center;
1816
+ color: #94a3b8;
1817
+ margin-bottom: 2rem;
1818
+ font-size: 1.1rem;
1819
+ }
1820
+
1821
+ .summary-cards {
1822
+ display: flex;
1823
+ gap: 1rem;
1824
+ margin-bottom: 2rem;
1825
+ flex-wrap: wrap;
1826
+ justify-content: center;
1827
+ }
1828
+
1829
+ .summary-card {
1830
+ padding: 1.25rem 2rem;
1831
+ border-radius: 12px;
1832
+ text-align: center;
1833
+ min-width: 140px;
1834
+ backdrop-filter: blur(10px);
1835
+ border: 1px solid rgba(255,255,255,0.1);
1836
+ }
1837
+
1838
+ .summary-card .count {
1839
+ font-size: 2.5rem;
1840
+ font-weight: 700;
1841
+ line-height: 1;
1842
+ }
1843
+
1844
+ .summary-card .label {
1845
+ font-size: 0.8rem;
1846
+ text-transform: uppercase;
1847
+ letter-spacing: 0.1em;
1848
+ margin-top: 0.5rem;
1849
+ opacity: 0.9;
1850
+ }
1851
+
1852
+ .card-critical { background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.4); }
1853
+ .card-critical .count { color: #ef4444; }
1854
+ .card-critical .label { color: #fca5a5; }
1855
+
1856
+ .card-high { background: rgba(245, 158, 11, 0.2); border-color: rgba(245, 158, 11, 0.4); }
1857
+ .card-high .count { color: #f59e0b; }
1858
+ .card-high .label { color: #fcd34d; }
1859
+
1860
+ .card-moderate { background: rgba(59, 130, 246, 0.2); border-color: rgba(59, 130, 246, 0.4); }
1861
+ .card-moderate .count { color: #3b82f6; }
1862
+ .card-moderate .label { color: #93c5fd; }
1863
+
1864
+ .card-low { background: rgba(107, 114, 128, 0.2); border-color: rgba(107, 114, 128, 0.4); }
1865
+ .card-low .count { color: #9ca3af; }
1866
+ .card-low .label { color: #d1d5db; }
1867
+
1868
+ .findings-list {
1869
+ background: rgba(255, 255, 255, 0.05);
1870
+ border-radius: 12px;
1871
+ border: 1px solid rgba(255,255,255,0.1);
1872
+ overflow: hidden;
1873
+ backdrop-filter: blur(10px);
1874
+ }
1875
+
1876
+ .finding-item {
1877
+ padding: 1.25rem 1.5rem;
1878
+ border-bottom: 1px solid rgba(255,255,255,0.06);
1879
+ transition: background-color 0.2s;
1880
+ }
1881
+
1882
+ .finding-item:last-child { border-bottom: none; }
1883
+ .finding-item:hover { background: rgba(255,255,255,0.04); }
1884
+
1885
+ .finding-header {
1886
+ display: flex;
1887
+ align-items: center;
1888
+ gap: 1rem;
1889
+ margin-bottom: 0.5rem;
1890
+ flex-wrap: wrap;
1891
+ }
1892
+
1893
+ .severity-badge {
1894
+ padding: 0.2rem 0.75rem;
1895
+ border-radius: 9999px;
1896
+ font-size: 0.7rem;
1897
+ font-weight: 700;
1898
+ text-transform: uppercase;
1899
+ letter-spacing: 0.05em;
1900
+ }
1901
+
1902
+ .severity-critical { background: #ef4444; color: white; }
1903
+ .severity-high { background: #f59e0b; color: #1a1a2e; }
1904
+ .severity-moderate { background: #3b82f6; color: white; }
1905
+ .severity-low { background: #6b7280; color: white; }
1906
+
1907
+ .finding-name {
1908
+ font-size: 1.2rem;
1909
+ font-weight: 600;
1910
+ color: #f1f5f9;
1911
+ }
1912
+
1913
+ .finding-version {
1914
+ font-family: 'Courier New', monospace;
1915
+ font-size: 0.85rem;
1916
+ padding: 0.15rem 0.6rem;
1917
+ background: rgba(255,255,255,0.1);
1918
+ border-radius: 4px;
1919
+ color: #cbd5e1;
1920
+ }
1921
+
1922
+ .source-tag {
1923
+ font-size: 0.7rem;
1924
+ padding: 0.15rem 0.6rem;
1925
+ border-radius: 4px;
1926
+ font-weight: 600;
1927
+ text-transform: uppercase;
1928
+ letter-spacing: 0.05em;
1929
+ }
1930
+
1931
+ .source-known-malicious { background: rgba(239, 68, 68, 0.2); color: #fca5a5; border: 1px solid rgba(239, 68, 68, 0.3); }
1932
+ .source-npm-audit { background: rgba(139, 92, 246, 0.2); color: #c4b5fd; border: 1px solid rgba(139, 92, 246, 0.3); }
1933
+
1934
+ .finding-description {
1935
+ color: #94a3b8;
1936
+ font-size: 0.9rem;
1937
+ line-height: 1.5;
1938
+ padding-left: 0.25rem;
1939
+ }
1940
+
1941
+ .no-findings {
1942
+ padding: 4rem 2rem;
1943
+ text-align: center;
1944
+ }
1945
+
1946
+ .no-findings .checkmark {
1947
+ font-size: 4rem;
1948
+ margin-bottom: 1rem;
1949
+ }
1950
+
1951
+ .no-findings h2 {
1952
+ color: #22c55e;
1953
+ font-size: 1.5rem;
1954
+ margin-bottom: 0.5rem;
1955
+ }
1956
+
1957
+ .no-findings p {
1958
+ color: #94a3b8;
1959
+ }
1960
+ </style>
1961
+ </head>
1962
+ <body>
1963
+ <div class="container">
1964
+ <h1>Security Scan</h1>
1965
+ <p class="subtitle">{{projectName}}</p>
1966
+
1967
+ {{#hasFindings}}
1968
+ <div class="summary-cards">
1969
+ {{#hasCritical}}
1970
+ <div class="summary-card card-critical">
1971
+ <div class="count">{{criticalCount}}</div>
1972
+ <div class="label">Critical</div>
1973
+ </div>
1974
+ {{/hasCritical}}
1975
+ {{#hasHigh}}
1976
+ <div class="summary-card card-high">
1977
+ <div class="count">{{highCount}}</div>
1978
+ <div class="label">High</div>
1979
+ </div>
1980
+ {{/hasHigh}}
1981
+ {{#hasModerate}}
1982
+ <div class="summary-card card-moderate">
1983
+ <div class="count">{{moderateCount}}</div>
1984
+ <div class="label">Moderate</div>
1985
+ </div>
1986
+ {{/hasModerate}}
1987
+ {{#hasLow}}
1988
+ <div class="summary-card card-low">
1989
+ <div class="count">{{lowCount}}</div>
1990
+ <div class="label">Low</div>
1991
+ </div>
1992
+ {{/hasLow}}
1993
+ </div>
1994
+
1995
+ <div class="findings-list">
1996
+ {{#findings}}
1997
+ <div class="finding-item">
1998
+ <div class="finding-header">
1999
+ <span class="severity-badge severity-{{severity}}">{{severity}}</span>
2000
+ <span class="finding-name">{{name}}</span>
2001
+ <span class="finding-version">{{installedVersion}}</span>
2002
+ <span class="source-tag source-{{source}}">{{source}}</span>
2003
+ </div>
2004
+ <div class="finding-description">{{description}}</div>
2005
+ </div>
2006
+ {{/findings}}
2007
+ </div>
2008
+ {{/hasFindings}}
2009
+
2010
+ {{^hasFindings}}
2011
+ <div class="findings-list">
2012
+ <div class="no-findings">
2013
+ <div class="checkmark">&#10003;</div>
2014
+ <h2>All Clear</h2>
2015
+ <p>No known malicious packages or vulnerabilities were found.</p>
2016
+ </div>
2017
+ </div>
2018
+ {{/hasFindings}}
2019
+ </div>
2020
+ </body>
2021
+ </html>
2022
+ `;
2023
+ }
2024
+ function getPeerDepsTemplate() {
2025
+ return `
2026
+ <html>
2027
+ <head>
2028
+ <title>{{projectName}} - Peer Dependencies</title>
2029
+ <style>
2030
+ * {
2031
+ margin: 0;
2032
+ padding: 0;
2033
+ box-sizing: border-box;
2034
+ }
2035
+
2036
+ body {
2037
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
2038
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
2039
+ min-height: 100vh;
2040
+ padding: 2rem;
2041
+ }
2042
+
2043
+ .container {
2044
+ max-width: 1200px;
2045
+ margin: 0 auto;
2046
+ }
2047
+
2048
+ h1 {
2049
+ color: white;
2050
+ margin-bottom: 2rem;
2051
+ font-size: 2.5rem;
2052
+ text-align: center;
2053
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
2054
+ }
2055
+
2056
+ .peer-deps-list {
2057
+ background: white;
2058
+ border-radius: 12px;
2059
+ box-shadow: 0 10px 40px rgba(0,0,0,0.1);
2060
+ overflow: hidden;
2061
+ }
2062
+
2063
+ .peer-dep-item {
2064
+ padding: 1.5rem;
2065
+ border-bottom: 1px solid #e5e7eb;
2066
+ transition: background-color 0.2s;
2067
+ }
2068
+
2069
+ .peer-dep-item:last-child {
2070
+ border-bottom: none;
2071
+ }
2072
+
2073
+ .peer-dep-item:hover {
2074
+ background-color: #f9fafb;
2075
+ }
2076
+
2077
+ .peer-dep-header {
2078
+ display: flex;
2079
+ justify-content: space-between;
2080
+ align-items: center;
2081
+ margin-bottom: 0.75rem;
2082
+ }
2083
+
2084
+ .peer-dep-name {
2085
+ font-size: 1.25rem;
2086
+ font-weight: 600;
2087
+ color: #1f2937;
2088
+ }
2089
+
2090
+ .peer-dep-version {
2091
+ font-family: 'Courier New', monospace;
2092
+ padding: 0.25rem 0.75rem;
2093
+ background: #f3f4f6;
2094
+ border-radius: 6px;
2095
+ font-size: 0.875rem;
2096
+ color: #4b5563;
2097
+ }
2098
+
2099
+ .peer-dep-version.conflict {
2100
+ background: #fee2e2;
2101
+ color: #dc2626;
2102
+ font-weight: 600;
2103
+ }
2104
+
2105
+ .peer-dep-info {
2106
+ display: flex;
2107
+ justify-content: space-between;
2108
+ align-items: center;
2109
+ flex-wrap: wrap;
2110
+ gap: 1rem;
2111
+ }
2112
+
2113
+ .required-by {
2114
+ flex: 1;
2115
+ min-width: 200px;
2116
+ }
2117
+
2118
+ .required-by-label {
2119
+ font-size: 0.75rem;
2120
+ color: #6b7280;
2121
+ text-transform: uppercase;
2122
+ letter-spacing: 0.05em;
2123
+ margin-bottom: 0.25rem;
2124
+ }
2125
+
2126
+ .required-by-list {
2127
+ display: flex;
2128
+ flex-wrap: wrap;
2129
+ gap: 0.5rem;
2130
+ }
2131
+
2132
+ .required-by-tag {
2133
+ display: inline-block;
2134
+ padding: 0.25rem 0.5rem;
2135
+ background: #dbeafe;
2136
+ color: #1e40af;
2137
+ border-radius: 4px;
2138
+ font-size: 0.75rem;
2139
+ font-weight: 500;
2140
+ }
2141
+
2142
+ .install-btn {
2143
+ padding: 0.5rem 1.5rem;
2144
+ background: #3b82f6;
2145
+ color: white;
2146
+ border: none;
2147
+ border-radius: 6px;
2148
+ font-size: 0.875rem;
2149
+ font-weight: 500;
2150
+ cursor: pointer;
2151
+ transition: all 0.2s;
2152
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
2153
+ }
2154
+
2155
+ .install-btn:hover {
2156
+ background: #2563eb;
2157
+ transform: translateY(-1px);
2158
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.4);
2159
+ }
2160
+
2161
+ .install-btn:active {
2162
+ transform: translateY(0);
2163
+ }
2164
+
2165
+ .install-btn:disabled {
2166
+ background: #9ca3af;
2167
+ cursor: not-allowed;
2168
+ transform: none;
2169
+ box-shadow: none;
2170
+ }
2171
+
2172
+ .installed-badge {
2173
+ display: flex;
2174
+ align-items: center;
2175
+ gap: 0.5rem;
2176
+ padding: 0.5rem 1rem;
2177
+ background: #d1fae5;
2178
+ color: #065f46;
2179
+ border-radius: 6px;
2180
+ font-size: 0.875rem;
2181
+ font-weight: 600;
2182
+ }
2183
+
2184
+ .installed-by-dep-badge {
2185
+ display: flex;
2186
+ align-items: center;
2187
+ gap: 0.5rem;
2188
+ padding: 0.5rem 1rem;
2189
+ background: #e0e7ff;
2190
+ color: #3730a3;
2191
+ border-radius: 6px;
2192
+ font-size: 0.875rem;
2193
+ font-weight: 600;
2194
+ }
2195
+
2196
+ .checkmark {
2197
+ color: #10b981;
2198
+ font-size: 1.25rem;
2199
+ }
2200
+
2201
+ .message {
2202
+ margin-top: 0.5rem;
2203
+ padding: 0.5rem;
2204
+ border-radius: 4px;
2205
+ font-size: 0.875rem;
2206
+ display: none;
2207
+ }
2208
+
2209
+ .message.success {
2210
+ background: #d1fae5;
2211
+ color: #065f46;
2212
+ display: block;
2213
+ }
2214
+
2215
+ .message.error {
2216
+ background: #fee2e2;
2217
+ color: #dc2626;
2218
+ display: block;
2219
+ }
2220
+
2221
+ .no-deps {
2222
+ padding: 3rem;
2223
+ text-align: center;
2224
+ color: #6b7280;
2225
+ font-size: 1.125rem;
2226
+ }
2227
+ </style>
2228
+ </head>
2229
+ <body>
2230
+ <div class="container">
2231
+ <h1>Peer Dependencies for {{projectName}}</h1>
2232
+ <div class="peer-deps-list">
2233
+ {{#peerDeps}}
2234
+ <div class="peer-dep-item">
2235
+ <div class="peer-dep-header">
2236
+ <span class="peer-dep-name">{{name}}</span>
2237
+ <span class="peer-dep-version {{#isConflict}}conflict{{/isConflict}}">
2238
+ {{#isConflict}}Conflict: {{/isConflict}}{{version}}
2239
+ </span>
2240
+ </div>
2241
+ <div class="peer-dep-info">
2242
+ <div class="required-by">
2243
+ <div class="required-by-label">Required by:</div>
2244
+ <div class="required-by-list">
2245
+ {{#requiredBy}}
2246
+ <span class="required-by-tag">{{.}}</span>
2247
+ {{/requiredBy}}
2248
+ </div>
2249
+ </div>
2250
+ <div class="action-container">
2251
+ {{#isInstalled}}
2252
+ <div class="installed-badge">
2253
+ Installed
2254
+ </div>
2255
+ {{/isInstalled}}
2256
+ {{#isInstalledByDependency}}
2257
+ <div class="installed-by-dep-badge">
2258
+ Installed by dependency
2259
+ </div>
2260
+ {{/isInstalledByDependency}}
2261
+ {{^isInstalled}}
2262
+ {{^isInstalledByDependency}}
2263
+ <button class="install-btn" onclick="installPackage('{{name}}', '{{version}}', this)">
2264
+ npm install
2265
+ </button>
2266
+ <div class="message" id="msg-{{name}}"></div>
2267
+ {{/isInstalledByDependency}}
2268
+ {{/isInstalled}}
2269
+ </div>
2270
+ </div>
2271
+ </div>
2272
+ {{/peerDeps}}
2273
+ {{^peerDeps}}
2274
+ <div class="no-deps">
2275
+ No peer dependencies found!
2276
+ </div>
2277
+ {{/peerDeps}}
2278
+ </div>
2279
+ </div>
2280
+
2281
+ <script>
2282
+ async function installPackage(packageName, version, button) {
2283
+ const messageEl = document.getElementById('msg-' + packageName);
2284
+
2285
+ button.disabled = true;
2286
+ button.textContent = 'Installing...';
2287
+ messageEl.className = 'message';
2288
+ messageEl.textContent = '';
2289
+
2290
+ try {
2291
+ const response = await fetch('/install', {
2292
+ method: 'POST',
2293
+ headers: {
2294
+ 'Content-Type': 'application/json',
2295
+ },
2296
+ body: JSON.stringify({
2297
+ package: packageName,
2298
+ version: version
2299
+ })
2300
+ });
2301
+
2302
+ const result = await response.json();
2303
+
2304
+ if (result.success) {
2305
+ messageEl.className = 'message success';
2306
+ messageEl.textContent = result.message;
2307
+ button.style.display = 'none';
2308
+
2309
+ // Optionally reload after a delay
2310
+ setTimeout(() => {
2311
+ location.reload();
2312
+ }, 2000);
2313
+ } else {
2314
+ messageEl.className = 'message error';
2315
+ messageEl.textContent = result.message;
2316
+ button.disabled = false;
2317
+ button.textContent = 'npm install';
2318
+ }
2319
+ } catch (error) {
2320
+ messageEl.className = 'message error';
2321
+ messageEl.textContent = 'Failed to install package: ' + error.message;
2322
+ button.disabled = false;
2323
+ button.textContent = 'npm install';
2324
+ }
2325
+ }
2326
+ </script>
2327
+ </body>
2328
+ </html>
2329
+ `;
2330
+ }
2331
+ function getTemplate() {
2332
+ return `
2333
+ <html>
2334
+ <head>
2335
+ <title>{{name}}'s Dependency Graph</title>
2336
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/sigma.js/2.4.0/sigma.min.js"></script>
2337
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/graphology/0.25.4/graphology.umd.min.js"></script>
2338
+ <script type="text/javascript">
2339
+ function applyForceLayout(graph, iterations) {
2340
+ var nodes = graph.nodes();
2341
+ var edges = graph.edges();
2342
+
2343
+ // Physics constants - lower repulsion for better spacing
2344
+ var repulsionStrength = 50;
2345
+ var attractionStrength = 0.01;
2346
+ var damping = 0.5;
2347
+
2348
+ for (var iter = 0; iter < iterations; iter++) {
2349
+ var forces = {};
2350
+
2351
+ // Initialize forces
2352
+ nodes.forEach(function(nodeId) {
2353
+ forces[nodeId] = { x: 0, y: 0 };
2354
+ });
2355
+
2356
+ // Repulsive forces between all nodes
2357
+ for (var i = 0; i < nodes.length; i++) {
2358
+ for (var j = i + 1; j < nodes.length; j++) {
2359
+ var node1 = nodes[i];
2360
+ var node2 = nodes[j];
2361
+ var attrs1 = graph.getNodeAttributes(node1);
2362
+ var attrs2 = graph.getNodeAttributes(node2);
2363
+
2364
+ var dx = attrs2.x - attrs1.x;
2365
+ var dy = attrs2.y - attrs1.y;
2366
+ var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
2367
+ var force = repulsionStrength / (distance * distance);
2368
+
2369
+ var fx = (dx / distance) * force;
2370
+ var fy = (dy / distance) * force;
2371
+
2372
+ forces[node1].x -= fx;
2373
+ forces[node1].y -= fy;
2374
+ forces[node2].x += fx;
2375
+ forces[node2].y += fy;
2376
+ }
2377
+ }
2378
+
2379
+ // Attractive forces along edges
2380
+ edges.forEach(function(edgeId) {
2381
+ var edge = graph.extremities(edgeId);
2382
+ var source = edge.source || edge[0];
2383
+ var target = edge.target || edge[1];
2384
+ var attrs1 = graph.getNodeAttributes(source);
2385
+ var attrs2 = graph.getNodeAttributes(target);
2386
+
2387
+ var dx = attrs2.x - attrs1.x;
2388
+ var dy = attrs2.y - attrs1.y;
2389
+ var distance = Math.sqrt(dx * dx + dy * dy) || 0.1;
2390
+
2391
+ var fx = dx * attractionStrength;
2392
+ var fy = dy * attractionStrength;
2393
+
2394
+ forces[source].x += fx;
2395
+ forces[source].y += fy;
2396
+ forces[target].x -= fx;
2397
+ forces[target].y -= fy;
2398
+ });
2399
+
2400
+ // Apply forces with damping
2401
+ nodes.forEach(function(nodeId) {
2402
+ var attrs = graph.getNodeAttributes(nodeId);
2403
+ attrs.x += forces[nodeId].x * damping;
2404
+ attrs.y += forces[nodeId].y * damping;
2405
+ });
2406
+ }
2407
+ }
2408
+
2409
+ function renderGraph() {
2410
+ var nodes = {{{nodes}}};
2411
+ var edges = {{{edges}}};
2412
+
2413
+ var graph = new graphology.Graph();
2414
+
2415
+ nodes.forEach(function(node) {
2416
+ graph.addNode(node.key, {
2417
+ label: node.label,
2418
+ x: node.x,
2419
+ y: node.y,
2420
+ size: node.size,
2421
+ color: node.color
2422
+ });
2423
+ });
2424
+
2425
+ edges.forEach(function(edge, index) {
2426
+ graph.mergeEdge(edge.source, edge.target, {
2427
+ size: 2,
2428
+ color: '#94a3b8'
2429
+ });
2430
+ });
2431
+
2432
+ // Apply custom force-directed layout
2433
+ {{#applyForceLayout}}
2434
+ applyForceLayout(graph, 100);
2435
+ {{/applyForceLayout}}
2436
+
2437
+ var container = document.getElementById("dep-graph");
2438
+ var renderer = new Sigma(graph, container, {
2439
+ renderEdgeLabels: false,
2440
+ defaultNodeColor: '#3b82f6',
2441
+ defaultEdgeColor: '#94a3b8'
2442
+ });
2443
+ }
2444
+ </script>
2445
+ <style>
2446
+ body { margin: 0; padding: 0; }
2447
+ #dep-graph { width: 100vw; height: 100vh; background: #f8fafc; }
2448
+ </style>
2449
+ </head>
2450
+ <body onload="renderGraph()">
2451
+ <div id="dep-graph"></div>
2452
+ </body>
2453
+ </html>
729
2454
  `;
730
2455
  }