hulud-party-scanner 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,15 @@
1
+ # hulud-party-scanner
2
+
1
3
  Project integrity scanner for known vulnerabilities and suspicious patterns related to the Shai-Hulud supply-chain attack.
2
4
 
3
- This project is a Node.js implementation based on the original shell script from [sng-jroji/hulud-party](https://github.com/sng-jroji/hulud-party).
5
+ This project is a Node.js implementation based on the original shell script from [sngular/shai-hulud-integrity-scanner](https://github.com/sngular/shai-hulud-integrity-scanner).
6
+
7
+ ## Usage
8
+
9
+ To scan a project, run the following command in your terminal. You can target a specific directory or run it in your current directory.
10
+
11
+ ```bash
12
+ npx hulud-party-scanner "/path/to/your/project"
13
+ ```
14
+
15
+ Si no se especifica una ruta, el escáner se ejecutará en el directorio actual.
@@ -1,7 +1,3 @@
1
- # This file contains 517 confirmed compromised package versions from the complete JFrog analysis
2
- # The Shai-Hulud attack was self-replicating, so new compromised packages may still be discovered
3
- #
4
- # Sources: StepSecurity, Wiz.io, Semgrep, JFrog security advisories
5
1
  # Format: package_name:version
6
2
  #
7
3
  # MAINTAINING THIS LIST:
@@ -14,56 +10,39 @@
14
10
  # * https://socket.dev/blog/ongoing-supply-chain-attack-targets-crowdstrike-npm-packages
15
11
  # - Format: one package:version per line
16
12
  # - Lines starting with # are comments and will be ignored
17
- #
18
- # DO NOT REMOVE EXISTING PACKAGES - only add new ones as they are discovered
19
-
20
- # @ctrl namespace - primary targets
21
- @ctrl/tinycolor:4.1.0
22
- @ctrl/tinycolor:4.1.1
23
- @ctrl/tinycolor:4.1.2
24
- @ctrl/deluge:1.2.0
25
- @ctrl/deluge:7.2.1
26
- @ctrl/deluge:7.2.2
27
- @ctrl/golang-template:1.4.2
28
- @ctrl/golang-template:1.4.3
29
- @ctrl/magnet-link:4.0.3
30
- @ctrl/magnet-link:4.0.4
31
13
 
32
- # @ahmedhfarag namespace
33
14
  @ahmedhfarag/ngx-perfect-scrollbar:20.0.20
34
15
  @ahmedhfarag/ngx-virtual-scroller:4.0.4
35
-
36
- # @art-ws namespace - 16+ packages
37
16
  @art-ws/common:2.0.22
38
17
  @art-ws/common:2.0.28
39
18
  @art-ws/config-eslint:2.0.4
40
19
  @art-ws/config-eslint:2.0.5
41
20
  @art-ws/config-ts:2.0.7
42
21
  @art-ws/config-ts:2.0.8
22
+ @art-ws/db-context:2.0.21
43
23
  @art-ws/db-context:2.0.24
24
+ @art-ws/di-node:2.0.13
44
25
  @art-ws/di:2.0.28
45
26
  @art-ws/di:2.0.32
46
- @art-ws/di-node:2.0.13
47
27
  @art-ws/eslint:1.0.5
48
28
  @art-ws/eslint:1.0.6
49
29
  @art-ws/fastify-http-server:2.0.24
50
30
  @art-ws/fastify-http-server:2.0.27
51
31
  @art-ws/http-server:2.0.21
52
32
  @art-ws/http-server:2.0.25
53
- @art-ws/openapi:0.1.9
54
33
  @art-ws/openapi:0.1.12
34
+ @art-ws/openapi:0.1.9
55
35
  @art-ws/package-base:1.0.5
56
36
  @art-ws/package-base:1.0.6
57
37
  @art-ws/prettier:1.0.5
58
38
  @art-ws/prettier:1.0.6
59
39
  @art-ws/slf:2.0.15
60
40
  @art-ws/slf:2.0.22
61
- @art-ws/ssl-info:1.0.9
62
41
  @art-ws/ssl-info:1.0.10
42
+ @art-ws/ssl-info:1.0.9
63
43
  @art-ws/web-app:1.0.3
64
44
  @art-ws/web-app:1.0.4
65
-
66
- # @crowdstrike namespace - 25+ packages
45
+ @basic-ui-components-stc/basic-ui-components:1.0.5
67
46
  @crowdstrike/commitlint:8.1.1
68
47
  @crowdstrike/commitlint:8.1.2
69
48
  @crowdstrike/falcon-shoelace:0.4.1
@@ -82,12 +61,104 @@
82
61
  @crowdstrike/logscale-search:1.205.2
83
62
  @crowdstrike/tailwind-toucan-base:5.0.1
84
63
  @crowdstrike/tailwind-toucan-base:5.0.2
85
-
86
- # @hestjs namespace
87
- @hestjs/core:1.0.1
88
- @hestjs/core:1.0.2
89
-
90
- # @nstudio namespace - multiple packages with various versions
64
+ @ctrl/deluge:7.2.1
65
+ @ctrl/deluge:7.2.2
66
+ @ctrl/golang-template:1.4.2
67
+ @ctrl/golang-template:1.4.3
68
+ @ctrl/magnet-link:4.0.3
69
+ @ctrl/magnet-link:4.0.4
70
+ @ctrl/ngx-codemirror:7.0.1
71
+ @ctrl/ngx-codemirror:7.0.2
72
+ @ctrl/ngx-csv:6.0.1
73
+ @ctrl/ngx-csv:6.0.2
74
+ @ctrl/ngx-emoji-mart:9.2.1
75
+ @ctrl/ngx-emoji-mart:9.2.2
76
+ @ctrl/ngx-rightclick:4.0.1
77
+ @ctrl/ngx-rightclick:4.0.2
78
+ @ctrl/qbittorrent:9.7.1
79
+ @ctrl/qbittorrent:9.7.2
80
+ @ctrl/react-adsense:2.0.1
81
+ @ctrl/react-adsense:2.0.2
82
+ @ctrl/shared-torrent:6.3.1
83
+ @ctrl/shared-torrent:6.3.2
84
+ @ctrl/tinycolor:4.1.1
85
+ @ctrl/tinycolor:4.1.2
86
+ @ctrl/torrent-file:4.1.1
87
+ @ctrl/torrent-file:4.1.2
88
+ @ctrl/transmission:7.3.1
89
+ @ctrl/ts-base32:4.0.1
90
+ @ctrl/ts-base32:4.0.2
91
+ @hestjs/core:0.2.1
92
+ @hestjs/cqrs:0.1.6
93
+ @hestjs/demo:0.1.2
94
+ @hestjs/eslint-config:0.1.2
95
+ @hestjs/logger:0.1.6
96
+ @hestjs/scalar:0.1.7
97
+ @hestjs/validation:0.1.6
98
+ @nativescript-community/arraybuffers:1.1.6
99
+ @nativescript-community/arraybuffers:1.1.7
100
+ @nativescript-community/arraybuffers:1.1.8
101
+ @nativescript-community/gesturehandler:2.0.35
102
+ @nativescript-community/perms:3.0.5
103
+ @nativescript-community/perms:3.0.6
104
+ @nativescript-community/perms:3.0.7
105
+ @nativescript-community/perms:3.0.8
106
+ @nativescript-community/perms:3.0.9
107
+ @nativescript-community/sentry:4.6.43
108
+ @nativescript-community/sqlite:3.5.3
109
+ @nativescript-community/sqlite:3.5.4
110
+ @nativescript-community/sqlite:3.5.5
111
+ @nativescript-community/text:1.6.10
112
+ @nativescript-community/text:1.6.11
113
+ @nativescript-community/text:1.6.12
114
+ @nativescript-community/text:1.6.13
115
+ @nativescript-community/text:1.6.9
116
+ @nativescript-community/typeorm:0.2.30
117
+ @nativescript-community/typeorm:0.2.31
118
+ @nativescript-community/typeorm:0.2.32
119
+ @nativescript-community/typeorm:0.2.33
120
+ @nativescript-community/ui-collectionview:6.0.6
121
+ @nativescript-community/ui-document-picker:1.1.27
122
+ @nativescript-community/ui-document-picker:1.1.28
123
+ @nativescript-community/ui-drawer:0.1.30
124
+ @nativescript-community/ui-image:4.5.6
125
+ @nativescript-community/ui-label:1.3.35
126
+ @nativescript-community/ui-label:1.3.36
127
+ @nativescript-community/ui-label:1.3.37
128
+ @nativescript-community/ui-material-bottom-navigation:7.2.72
129
+ @nativescript-community/ui-material-bottom-navigation:7.2.73
130
+ @nativescript-community/ui-material-bottom-navigation:7.2.74
131
+ @nativescript-community/ui-material-bottom-navigation:7.2.75
132
+ @nativescript-community/ui-material-bottomsheet:7.2.72
133
+ @nativescript-community/ui-material-core-tabs:7.2.72
134
+ @nativescript-community/ui-material-core-tabs:7.2.73
135
+ @nativescript-community/ui-material-core-tabs:7.2.74
136
+ @nativescript-community/ui-material-core-tabs:7.2.75
137
+ @nativescript-community/ui-material-core-tabs:7.2.76
138
+ @nativescript-community/ui-material-core:7.2.72
139
+ @nativescript-community/ui-material-core:7.2.73
140
+ @nativescript-community/ui-material-core:7.2.74
141
+ @nativescript-community/ui-material-core:7.2.75
142
+ @nativescript-community/ui-material-core:7.2.76
143
+ @nativescript-community/ui-material-ripple:7.2.72
144
+ @nativescript-community/ui-material-ripple:7.2.73
145
+ @nativescript-community/ui-material-ripple:7.2.74
146
+ @nativescript-community/ui-material-ripple:7.2.75
147
+ @nativescript-community/ui-material-tabs:7.2.72
148
+ @nativescript-community/ui-material-tabs:7.2.73
149
+ @nativescript-community/ui-material-tabs:7.2.74
150
+ @nativescript-community/ui-material-tabs:7.2.75
151
+ @nativescript-community/ui-pager:14.1.35
152
+ @nativescript-community/ui-pager:14.1.36
153
+ @nativescript-community/ui-pager:14.1.37
154
+ @nativescript-community/ui-pager:14.1.38
155
+ @nativescript-community/ui-pulltorefresh:2.5.4
156
+ @nativescript-community/ui-pulltorefresh:2.5.5
157
+ @nativescript-community/ui-pulltorefresh:2.5.6
158
+ @nativescript-community/ui-pulltorefresh:2.5.7
159
+ @nexe/config-manager:0.1.1
160
+ @nexe/eslint-config:0.1.1
161
+ @nexe/logger:0.1.3
91
162
  @nstudio/angular:20.0.4
92
163
  @nstudio/angular:20.0.5
93
164
  @nstudio/angular:20.0.6
@@ -108,10 +179,15 @@
108
179
  @nstudio/ui-collectionview:5.1.14
109
180
  @nstudio/web-angular:20.0.4
110
181
  @nstudio/web:20.0.4
182
+ @nstudio/xplat-utils:20.0.4
111
183
  @nstudio/xplat-utils:20.0.5
112
184
  @nstudio/xplat-utils:20.0.6
113
-
114
- # @operato namespace - multiple packages with 9.0.x versions
185
+ @nstudio/xplat-utils:20.0.7
186
+ @nstudio/xplat:20.0.4
187
+ @nstudio/xplat:20.0.5
188
+ @nstudio/xplat:20.0.6
189
+ @nstudio/xplat:20.0.7
190
+ @operato/board:9.0.35
115
191
  @operato/board:9.0.36
116
192
  @operato/board:9.0.37
117
193
  @operato/board:9.0.38
@@ -123,6 +199,11 @@
123
199
  @operato/board:9.0.44
124
200
  @operato/board:9.0.45
125
201
  @operato/board:9.0.46
202
+ @operato/board:9.0.47
203
+ @operato/board:9.0.48
204
+ @operato/board:9.0.49
205
+ @operato/board:9.0.50
206
+ @operato/board:9.0.51
126
207
  @operato/data-grist:9.0.29
127
208
  @operato/data-grist:9.0.35
128
209
  @operato/data-grist:9.0.36
@@ -140,6 +221,11 @@
140
221
  @operato/graphql:9.0.44
141
222
  @operato/graphql:9.0.45
142
223
  @operato/graphql:9.0.46
224
+ @operato/graphql:9.0.47
225
+ @operato/graphql:9.0.48
226
+ @operato/graphql:9.0.49
227
+ @operato/graphql:9.0.50
228
+ @operato/graphql:9.0.51
143
229
  @operato/headroom:9.0.2
144
230
  @operato/headroom:9.0.35
145
231
  @operato/headroom:9.0.36
@@ -156,10 +242,14 @@
156
242
  @operato/help:9.0.44
157
243
  @operato/help:9.0.45
158
244
  @operato/help:9.0.46
245
+ @operato/help:9.0.47
246
+ @operato/help:9.0.48
247
+ @operato/help:9.0.49
248
+ @operato/help:9.0.50
249
+ @operato/help:9.0.51
159
250
  @operato/i18n:9.0.35
160
251
  @operato/i18n:9.0.36
161
252
  @operato/i18n:9.0.37
162
- @operato/input:9.0.27
163
253
  @operato/input:9.0.35
164
254
  @operato/input:9.0.36
165
255
  @operato/input:9.0.37
@@ -172,10 +262,10 @@
172
262
  @operato/input:9.0.44
173
263
  @operato/input:9.0.45
174
264
  @operato/input:9.0.46
265
+ @operato/input:9.0.47
266
+ @operato/input:9.0.48
175
267
  @operato/layout:9.0.35
176
- @operato/layout:9.0.36
177
268
  @operato/layout:9.0.37
178
- @operato/popup:9.0.22
179
269
  @operato/popup:9.0.35
180
270
  @operato/popup:9.0.36
181
271
  @operato/popup:9.0.37
@@ -188,6 +278,12 @@
188
278
  @operato/popup:9.0.44
189
279
  @operato/popup:9.0.45
190
280
  @operato/popup:9.0.46
281
+ @operato/popup:9.0.47
282
+ @operato/popup:9.0.48
283
+ @operato/popup:9.0.49
284
+ @operato/popup:9.0.50
285
+ @operato/popup:9.0.51
286
+ @operato/pull-to-refresh:9.0.35
191
287
  @operato/pull-to-refresh:9.0.36
192
288
  @operato/pull-to-refresh:9.0.37
193
289
  @operato/pull-to-refresh:9.0.38
@@ -195,6 +291,11 @@
195
291
  @operato/pull-to-refresh:9.0.40
196
292
  @operato/pull-to-refresh:9.0.41
197
293
  @operato/pull-to-refresh:9.0.42
294
+ @operato/pull-to-refresh:9.0.43
295
+ @operato/pull-to-refresh:9.0.44
296
+ @operato/pull-to-refresh:9.0.45
297
+ @operato/pull-to-refresh:9.0.46
298
+ @operato/pull-to-refresh:9.0.47
198
299
  @operato/shell:9.0.22
199
300
  @operato/shell:9.0.35
200
301
  @operato/shell:9.0.36
@@ -218,8 +319,11 @@
218
319
  @operato/utils:9.0.44
219
320
  @operato/utils:9.0.45
220
321
  @operato/utils:9.0.46
221
-
222
- # @teselagen namespace - multiple packages with 0.x versions
322
+ @operato/utils:9.0.47
323
+ @operato/utils:9.0.48
324
+ @operato/utils:9.0.49
325
+ @operato/utils:9.0.50
326
+ @operato/utils:9.0.51
223
327
  @teselagen/bio-parsers:0.4.29
224
328
  @teselagen/bio-parsers:0.4.30
225
329
  @teselagen/bounce-loader:0.3.16
@@ -239,10 +343,10 @@
239
343
  @teselagen/react-table:6.10.22
240
344
  @teselagen/sequence-utils:0.3.33
241
345
  @teselagen/sequence-utils:0.3.34
242
- @teselagen/ui:0.9.9
243
346
  @teselagen/ui:0.9.10
244
-
245
- # @things-factory namespace - multiple packages with 9.0.x versions
347
+ @teselagen/ui:0.9.9
348
+ @thangved/callback-window:1.1.4
349
+ @things-factory/attachment-base:9.0.42
246
350
  @things-factory/attachment-base:9.0.43
247
351
  @things-factory/attachment-base:9.0.44
248
352
  @things-factory/attachment-base:9.0.45
@@ -251,6 +355,12 @@
251
355
  @things-factory/attachment-base:9.0.48
252
356
  @things-factory/attachment-base:9.0.49
253
357
  @things-factory/attachment-base:9.0.50
358
+ @things-factory/attachment-base:9.0.51
359
+ @things-factory/attachment-base:9.0.52
360
+ @things-factory/attachment-base:9.0.53
361
+ @things-factory/attachment-base:9.0.54
362
+ @things-factory/attachment-base:9.0.55
363
+ @things-factory/auth-base:9.0.42
254
364
  @things-factory/auth-base:9.0.43
255
365
  @things-factory/auth-base:9.0.44
256
366
  @things-factory/auth-base:9.0.45
@@ -267,178 +377,23 @@
267
377
  @things-factory/email-base:9.0.52
268
378
  @things-factory/email-base:9.0.53
269
379
  @things-factory/email-base:9.0.54
380
+ @things-factory/email-base:9.0.55
381
+ @things-factory/email-base:9.0.56
382
+ @things-factory/email-base:9.0.57
383
+ @things-factory/email-base:9.0.58
384
+ @things-factory/email-base:9.0.59
270
385
  @things-factory/env:9.0.42
271
386
  @things-factory/env:9.0.43
272
387
  @things-factory/env:9.0.44
273
388
  @things-factory/env:9.0.45
389
+ @things-factory/integration-base:9.0.42
274
390
  @things-factory/integration-base:9.0.43
275
391
  @things-factory/integration-base:9.0.44
276
392
  @things-factory/integration-base:9.0.45
393
+ @things-factory/integration-marketplace:9.0.42
277
394
  @things-factory/integration-marketplace:9.0.43
278
395
  @things-factory/integration-marketplace:9.0.44
279
396
  @things-factory/integration-marketplace:9.0.45
280
-
281
- # @nativescript-community namespace - 40+ packages
282
- @nativescript-community/push:1.0.0
283
- @nativescript-community/sqlite:3.5.2
284
- @nativescript-community/sqlite:3.5.3
285
- @nativescript-community/sqlite:3.5.4
286
- @nativescript-community/sqlite:3.5.5
287
- @nativescript-community/ui-material-activityindicator:7.2.49
288
- @nativescript-community/ui-material-bottomnavigationbar:7.2.49
289
- @nativescript-community/ui-material-bottomsheet:7.2.49
290
- @nativescript-community/ui-material-button:7.2.49
291
- @nativescript-community/ui-material-cardview:7.2.49
292
- @nativescript-community/ui-material-core:7.2.49
293
- @nativescript-community/ui-material-dialogs:7.2.49
294
- @nativescript-community/ui-material-floatingactionbutton:7.2.49
295
- @nativescript-community/ui-material-progress:7.2.49
296
- @nativescript-community/ui-material-ripple:7.2.49
297
- @nativescript-community/ui-material-slider:7.2.49
298
- @nativescript-community/ui-material-snackbar:7.2.49
299
- @nativescript-community/ui-material-tabs:7.2.49
300
- @nativescript-community/ui-material-textfield:7.2.49
301
- @nativescript-community/ui-material-textview:7.2.49
302
-
303
- # Popular standalone packages compromised
304
- angulartics2:14.1.2
305
- koa2-swagger-ui:5.11.1
306
- koa2-swagger-ui:5.11.2
307
- ngx-bootstrap:18.1.4
308
- ngx-bootstrap:19.0.3
309
- ngx-bootstrap:20.0.4
310
- ngx-bootstrap:20.0.5
311
- ngx-bootstrap:20.0.6
312
- ngx-toastr:19.0.1
313
- ngx-toastr:19.0.2
314
-
315
- # Additional compromised packages from JFrog comprehensive list
316
- @art-ws/db-context:2.0.21
317
- @basic-ui-components-stc/basic-ui-components:1.0.5
318
- @ctrl/ngx-codemirror:7.0.1
319
- @ctrl/ngx-csv:6.0.1
320
- @ctrl/ngx-emoji-mart:9.2.1
321
- @ctrl/ngx-rightclick:4.0.1
322
- @ctrl/qbittorrent:9.7.1
323
- @ctrl/react-adsense:2.0.1
324
- @ctrl/shared-torrent:6.3.1
325
- @ctrl/torrent-file:4.1.1
326
- @ctrl/ts-base32:4.0.1
327
- @hestjs/core:0.2.1
328
- @hestjs/cqrs:0.1.6
329
- @hestjs/demo:0.1.2
330
- @hestjs/eslint-config:0.1.2
331
- @hestjs/logger:0.1.6
332
- @hestjs/scalar:0.1.7
333
- @hestjs/validation:0.1.6
334
- @nativescript-community/arraybuffers:1.1.6
335
- @nativescript-community/arraybuffers:1.1.7
336
- @nativescript-community/arraybuffers:1.1.8
337
- @nativescript-community/perms:3.0.5
338
- @nativescript-community/perms:3.0.6
339
- @nativescript-community/perms:3.0.7
340
- @nativescript-community/perms:3.0.8
341
- @nativescript-community/perms:3.0.9
342
- @nativescript-community/sentry:4.6.43
343
- @nativescript-community/text:1.6.10
344
- @nativescript-community/text:1.6.11
345
- @nativescript-community/text:1.6.12
346
- @nativescript-community/text:1.6.9
347
- @nativescript-community/typeorm:0.2.30
348
- @nativescript-community/typeorm:0.2.31
349
- @nativescript-community/typeorm:0.2.32
350
- @nativescript-community/typeorm:0.2.33
351
- @nativescript-community/ui-document-picker:1.1.27
352
- @nativescript-community/ui-document-picker:1.1.28
353
- @nativescript-community/ui-label:1.3.35
354
- @nativescript-community/ui-label:1.3.36
355
- @nativescript-community/ui-label:1.3.37
356
- @nativescript-community/ui-material-bottom-navigation:7.2.72
357
- @nativescript-community/ui-material-bottom-navigation:7.2.73
358
- @nativescript-community/ui-material-bottom-navigation:7.2.74
359
- @nativescript-community/ui-material-bottom-navigation:7.2.75
360
- @nativescript-community/ui-material-core-tabs:7.2.72
361
- @nativescript-community/ui-material-core-tabs:7.2.73
362
- @nativescript-community/ui-material-core-tabs:7.2.74
363
- @nativescript-community/ui-material-core-tabs:7.2.75
364
- @nativescript-community/ui-material-core:7.2.72
365
- @nativescript-community/ui-material-core:7.2.73
366
- @nativescript-community/ui-material-core:7.2.74
367
- @nativescript-community/ui-material-core:7.2.75
368
- @nativescript-community/ui-material-ripple:7.2.72
369
- @nativescript-community/ui-material-ripple:7.2.73
370
- @nativescript-community/ui-material-ripple:7.2.74
371
- @nativescript-community/ui-material-ripple:7.2.75
372
- @nativescript-community/ui-material-tabs:7.2.72
373
- @nativescript-community/ui-material-tabs:7.2.73
374
- @nativescript-community/ui-material-tabs:7.2.74
375
- @nativescript-community/ui-material-tabs:7.2.75
376
- @nativescript-community/ui-pager:14.1.35
377
- @nativescript-community/ui-pager:14.1.36
378
- @nativescript-community/ui-pager:14.1.37
379
- @nativescript-community/ui-pager:14.1.38
380
- @nativescript-community/ui-pulltorefresh:2.5.4
381
- @nativescript-community/ui-pulltorefresh:2.5.5
382
- @nativescript-community/ui-pulltorefresh:2.5.6
383
- @nativescript-community/ui-pulltorefresh:2.5.7
384
- @nexe/config-manager:0.1.1
385
- @nexe/eslint-config:0.1.1
386
- @nexe/logger:0.1.3
387
- @nstudio/xplat-utils:20.0.4
388
- @nstudio/xplat-utils:20.0.7
389
- @nstudio/xplat:20.0.4
390
- @nstudio/xplat:20.0.5
391
- @nstudio/xplat:20.0.6
392
- @nstudio/xplat:20.0.7
393
- @operato/board:9.0.35
394
- @operato/board:9.0.47
395
- @operato/board:9.0.48
396
- @operato/board:9.0.49
397
- @operato/board:9.0.50
398
- @operato/board:9.0.51
399
- @operato/graphql:9.0.47
400
- @operato/graphql:9.0.48
401
- @operato/graphql:9.0.49
402
- @operato/graphql:9.0.50
403
- @operato/graphql:9.0.51
404
- @operato/help:9.0.47
405
- @operato/help:9.0.48
406
- @operato/help:9.0.49
407
- @operato/help:9.0.50
408
- @operato/help:9.0.51
409
- @operato/input:9.0.47
410
- @operato/input:9.0.48
411
- @operato/popup:9.0.47
412
- @operato/popup:9.0.48
413
- @operato/popup:9.0.49
414
- @operato/popup:9.0.50
415
- @operato/popup:9.0.51
416
- @operato/pull-to-refresh:9.0.35
417
- @operato/pull-to-refresh:9.0.43
418
- @operato/pull-to-refresh:9.0.44
419
- @operato/pull-to-refresh:9.0.45
420
- @operato/pull-to-refresh:9.0.46
421
- @operato/pull-to-refresh:9.0.47
422
- @operato/utils:9.0.47
423
- @operato/utils:9.0.48
424
- @operato/utils:9.0.49
425
- @operato/utils:9.0.50
426
- @operato/utils:9.0.51
427
- @thangved/callback-window:1.1.4
428
- @things-factory/attachment-base:9.0.42
429
- @things-factory/attachment-base:9.0.51
430
- @things-factory/attachment-base:9.0.52
431
- @things-factory/attachment-base:9.0.53
432
- @things-factory/attachment-base:9.0.54
433
- @things-factory/attachment-base:9.0.55
434
- @things-factory/auth-base:9.0.42
435
- @things-factory/email-base:9.0.55
436
- @things-factory/email-base:9.0.56
437
- @things-factory/email-base:9.0.57
438
- @things-factory/email-base:9.0.58
439
- @things-factory/email-base:9.0.59
440
- @things-factory/integration-base:9.0.42
441
- @things-factory/integration-marketplace:9.0.42
442
397
  @things-factory/shell:9.0.42
443
398
  @things-factory/shell:9.0.43
444
399
  @things-factory/shell:9.0.44
@@ -457,6 +412,7 @@ ace-colorpicker-rpk:0.0.14
457
412
  airchief:0.3.1
458
413
  airpilot:0.8.8
459
414
  angulartics2:14.1.1
415
+ angulartics2:14.1.2
460
416
  browser-webdriver-downloader:3.0.8
461
417
  capacitor-notificationhandler:0.0.2
462
418
  capacitor-notificationhandler:0.0.3
@@ -490,6 +446,7 @@ ember-velcro:2.2.2
490
446
  encounter-playground:0.0.2
491
447
  encounter-playground:0.0.3
492
448
  encounter-playground:0.0.4
449
+ encounter-playground:0.0.5
493
450
  eslint-config-crowdstrike-node:4.0.3
494
451
  eslint-config-crowdstrike-node:4.0.4
495
452
  eslint-config-crowdstrike:11.0.2
@@ -505,6 +462,8 @@ json-rules-engine-simplified:0.2.2
505
462
  json-rules-engine-simplified:0.2.3
506
463
  json-rules-engine-simplified:0.2.4
507
464
  jumpgate:0.0.2
465
+ koa2-swagger-ui:5.11.1
466
+ koa2-swagger-ui:5.11.2
508
467
  mcfly-semantic-release:1.3.1
509
468
  mcp-knowledge-base:0.0.2
510
469
  mcp-knowledge-graph:1.2.1
@@ -522,9 +481,18 @@ ng2-file-upload:8.0.1
522
481
  ng2-file-upload:8.0.2
523
482
  ng2-file-upload:8.0.3
524
483
  ng2-file-upload:9.0.1
484
+ ngx-bootstrap:18.1.4
485
+ ngx-bootstrap:19.0.3
525
486
  ngx-bootstrap:19.0.4
526
487
  ngx-bootstrap:20.0.3
488
+ ngx-bootstrap:20.0.4
489
+ ngx-bootstrap:20.0.5
490
+ ngx-bootstrap:20.0.6
527
491
  ngx-color:10.0.1
492
+ ngx-color:10.0.2
493
+ ngx-toastr:19.0.1
494
+ ngx-toastr:19.0.2
495
+ ngx-trend:8.0.1
528
496
  ngx-ws:1.1.5
529
497
  ngx-ws:1.1.6
530
498
  oradm-to-gql:35.0.14
@@ -541,12 +509,15 @@ printjs-rpk:1.6.1
541
509
  react-complaint-image:0.0.32
542
510
  react-complaint-image:0.0.33
543
511
  react-complaint-image:0.0.34
512
+ react-complaint-image:0.0.35
544
513
  react-jsonschema-form-conditionals:0.3.18
545
514
  react-jsonschema-form-conditionals:0.3.19
546
515
  react-jsonschema-form-conditionals:0.3.20
516
+ react-jsonschema-form-conditionals:0.3.21
547
517
  react-jsonschema-form-extras:1.0.1
548
518
  react-jsonschema-form-extras:1.0.2
549
519
  react-jsonschema-form-extras:1.0.3
520
+ react-jsonschema-form-extras:1.0.4
550
521
  react-jsonschema-rxnt-extras:0.4.6
551
522
  react-jsonschema-rxnt-extras:0.4.7
552
523
  react-jsonschema-rxnt-extras:0.4.8
@@ -556,13 +527,17 @@ remark-preset-lint-crowdstrike:4.0.2
556
527
  rxnt-authentication:0.0.3
557
528
  rxnt-authentication:0.0.4
558
529
  rxnt-authentication:0.0.5
530
+ rxnt-authentication:0.0.6
559
531
  rxnt-healthchecks-nestjs:1.0.2
560
532
  rxnt-healthchecks-nestjs:1.0.3
561
533
  rxnt-healthchecks-nestjs:1.0.4
534
+ rxnt-healthchecks-nestjs:1.0.5
562
535
  rxnt-kue:1.0.4
563
536
  rxnt-kue:1.0.5
564
537
  rxnt-kue:1.0.6
538
+ rxnt-kue:1.0.7
565
539
  swc-plugin-component-annotate:1.9.1
540
+ swc-plugin-component-annotate:1.9.2
566
541
  tbssnch:1.0.2
567
542
  teselagen-interval-tree:1.1.2
568
543
  tg-client-query-builder:2.14.4
@@ -573,6 +548,7 @@ tg-seq-gen:1.0.10
573
548
  tg-seq-gen:1.0.9
574
549
  thangved-react-grid:1.0.3
575
550
  ts-gaussian:3.0.5
551
+ ts-gaussian:3.0.6
576
552
  ts-imports:1.0.1
577
553
  ts-imports:1.0.2
578
554
  tvi-cli:0.1.5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hulud-party-scanner",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "preferGlobal": true,
5
5
  "bin": {
6
6
  "hulud-party-scanner": "./scan.js"
@@ -13,4 +13,4 @@
13
13
  "author": "miguel.sngular",
14
14
  "license": "ISC",
15
15
  "repository": "https://github.com/migohe14/hulud-scanner"
16
- }
16
+ }
package/scan.js CHANGED
@@ -1,29 +1,69 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  const fs = require('fs');
3
3
  const https = require('https');
4
4
  const path = require('path');
5
5
  const crypto = require('crypto');
6
-
6
+ const { execSync } = require('child_process');
7
+
7
8
  // --- Configuration ---
8
- const COMPROMISED_LIST_URL = "https://raw.githubusercontent.com/migohe14/hulud-scanner/refs/heads/main/compromised-libs.txt";
9
- const MALICIOUS_HASH = "46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09";
9
+ const COMPROMISED_LIST_URL = "https://raw.githubusercontent.com/sngular/shai-hulud-integrity-scanner/refs/heads/main/compromised-libs.txt";
10
+ const MALICIOUS_HASHES = new Set([
11
+ "46faab8ab153fae6e80e7cca38eab363075bb524edd79e42269217a083628f09",
12
+ "de0e25a3e6c1e1e5998b306b7141b3dc4c0088da9d7bb47c1c00c91e6e4f85d6",
13
+ "81d2a004a1bca6ef87a1caf7d0e0b355ad1764238e40ff6d1b1cb77ad4f595c3",
14
+ "83a650ce44b2a9854802a7fb4c202877815274c129af49e6c2d1d5d5d55c501e",
15
+ "4b2399646573bb737c4969563303d8ee2e9ddbd1b271f1ca9e35ea78062538db",
16
+ "dc67467a39b70d1cd4c1f7f7a459b35058163592f4a9e8fb4dffcbba98ef210c",
17
+ "b74caeaa75e077c99f7d44f46daaf9796a3be43ecf24f2a1fd381844669da777",
18
+ "86532ed94c5804e1ca32fa67257e1bb9de628e3e48a1f56e67042dc055effb5b",
19
+ "aba1fcbd15c6ba6d9b96e34cec287660fff4a31632bf76f2a766c499f55ca1ee",
20
+ ]);
21
+ const COMPROMISED_NAMESPACES = [
22
+ "@crowdstrike", "@art-ws", "@ngx", "@ctrl", "@nativescript-community",
23
+ "@ahmedhfarag", "@operato", "@teselagen", "@things-factory", "@hestjs",
24
+ "@nstudio", "@basic-ui-components-stc", "@nexe", "@thangved",
25
+ "@tnf-dev", "@ui-ux-gang", "@yoobic"
26
+ ];
27
+ const EXFIL_PATTERNS = ['webhook.site', 'bb8ca5f6-4175-45d2-b042-fc9ebb8170b7', 'exfiltrat'];
28
+ const ENV_PATTERNS = ['process\\.env', 'os\\.environ', 'getenv', 'AWS_ACCESS_KEY', 'GITHUB_TOKEN', 'NPM_TOKEN'];
10
29
 
11
30
  // --- Console Colors ---
12
31
  const colors = {
13
32
  RED: '\x1b[31m',
14
33
  GREEN: '\x1b[32m',
15
34
  YELLOW: '\x1b[33m',
16
- CYAN: '\x1b[36m',
17
35
  BLUE: '\x1b[34m',
36
+ BOLD: '\x1b[1m',
18
37
  RESET: '\x1b[0m'
19
38
  };
20
39
 
40
+ const log = {
41
+ info: (msg) => console.log(`${colors.GREEN}INFO:${colors.RESET} ${msg}`),
42
+ warn: (msg) => console.warn(`${colors.YELLOW}${colors.BOLD}WARN:${colors.RESET} ${msg}`),
43
+ error: (msg) => console.error(`${colors.RED}${colors.BOLD}ERROR:${colors.RESET} ${msg}`),
44
+ header: (msg) => console.log(`\n${colors.BLUE}${colors.BOLD}--- ${msg} ---${colors.RESET}`),
45
+ };
46
+
47
+ /**
48
+ * Checks if a command exists on the system.
49
+ * @param {string} cmd The command to check.
50
+ * @returns {boolean} True if the command exists.
51
+ */
52
+ function commandExists(cmd) {
53
+ try {
54
+ execSync(process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`, { stdio: 'ignore' });
55
+ return true;
56
+ } catch (e) {
57
+ return false;
58
+ }
59
+ }
60
+
21
61
  /**
22
62
  * Downloads the list of compromised packages from GitHub.
23
63
  * @returns {Promise<Set<string>>} A Set of packages in "name@version" format.
24
64
  */
25
65
  async function getCompromisedPackages() {
26
- console.log(`${colors.CYAN}🌐 Downloading compromised packages list...${colors.RESET}`);
66
+ log.info("Downloading compromised packages list...");
27
67
  return new Promise((resolve, reject) => {
28
68
  https.get(COMPROMISED_LIST_URL, (res) => {
29
69
  let data = '';
@@ -33,17 +73,7 @@ async function getCompromisedPackages() {
33
73
  .split('\n')
34
74
  .map(line => line.trim())
35
75
  .filter(line => line && !line.startsWith('#')) // Ignore comments and empty lines
36
- .map(line => line.replace(':', '@')) // Change 'pkg:1.0.0' to 'pkg@1.0.0'
37
- .reduce((acc, line) => {
38
- // Handle lines with multiple versions like 'pkg:1.0.0, 1.0.1'
39
- const [name, versions] = line.split('@');
40
- if (versions) {
41
- versions.split(',').forEach(version => {
42
- acc.add(`${name}@${version.trim()}`);
43
- });
44
- }
45
- return acc;
46
- }, new Set());
76
+ .map(line => line.replace(':', '@')); // Change 'pkg:1.0.0' to 'pkg@1.0.0'
47
77
  resolve(packages);
48
78
  });
49
79
  }).on('error', (err) => {
@@ -52,6 +82,48 @@ async function getCompromisedPackages() {
52
82
  });
53
83
  }
54
84
 
85
+ /**
86
+ * Parses dependencies from pnpm-lock.yaml by shelling out to `pnpm`.
87
+ * @param {string} projectPath The root of the project.
88
+ * @returns {Set<string>} A Set of local dependencies.
89
+ */
90
+ function parsePnpmLock(projectPath) {
91
+ log.info("Found pnpm-lock.yaml. Analyzing full dependency tree with PNPM...");
92
+ try {
93
+ const pnpmOutput = execSync('pnpm list --json --prod --dev', { cwd: projectPath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
94
+ const deps = JSON.parse(pnpmOutput);
95
+ const packages = new Set();
96
+ const projects = Array.isArray(deps) ? deps : [deps];
97
+ projects.forEach(project => {
98
+ if (!project.dependencies) return;
99
+ Object.entries(project.dependencies).forEach(([name, details]) => {
100
+ packages.add(`${name}@${details.version}`);
101
+ });
102
+ });
103
+ return packages;
104
+ } catch (e) {
105
+ log.warn("The 'pnpm list' command failed. Please run 'pnpm install'.");
106
+ return new Set();
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Parses dependencies from package.json as a fallback.
112
+ * @param {string} pkgJsonPath Path to package.json.
113
+ * @returns {Set<string>} A Set of local dependencies.
114
+ */
115
+ function parsePackageJson(pkgJsonPath) {
116
+ log.warn("No lockfile found. Falling back to package.json (will miss transitive dependencies).");
117
+ log.info("Scanning package.json...");
118
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
119
+ const packages = new Set();
120
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
121
+ for (const [name, version] of Object.entries(allDeps)) {
122
+ packages.add(`${name}@${version.replace(/[\^~]/g, '')}`);
123
+ }
124
+ return packages;
125
+ }
126
+
55
127
  /**
56
128
  * Extracts all dependencies from a package-lock.json file.
57
129
  * Supports v1/v2 (npm 6) and v2/v3 (npm 7+) formats.
@@ -59,7 +131,7 @@ async function getCompromisedPackages() {
59
131
  * @returns {Set<string>} A Set of local dependencies in "name@version" format.
60
132
  */
61
133
  function getLocalPackages(lockfilePath) {
62
- console.log(`${colors.CYAN}📦 Extracting dependencies from ${lockfilePath}...${colors.RESET}`);
134
+ log.info(`Extracting dependencies from ${path.basename(lockfilePath)}...`);
63
135
  if (!fs.existsSync(lockfilePath)) {
64
136
  throw new Error(`File not found: ${lockfilePath}`);
65
137
  }
@@ -67,9 +139,33 @@ function getLocalPackages(lockfilePath) {
67
139
  const lockfile = JSON.parse(fs.readFileSync(lockfilePath, 'utf-8'));
68
140
  const packages = new Set();
69
141
 
142
+ // Heuristic to check for yarn.lock v1 by looking for a characteristic header
143
+ if (path.basename(lockfilePath) === 'yarn.lock') {
144
+ log.info("Classic Yarn (v1) detected. Using 'yarn list'...");
145
+ try {
146
+ // Yarn v1 `list` is very slow, so we give it more time.
147
+ const yarnOutput = execSync('yarn list --json --no-progress', { cwd: path.dirname(lockfilePath), encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'], timeout: 120000 });
148
+ const data = JSON.parse(yarnOutput);
149
+ if (data.type === 'tree' && data.data && data.data.trees) {
150
+ const extractYarnDeps = (trees) => {
151
+ trees.forEach(tree => {
152
+ const [name, version] = tree.name.split('@');
153
+ if (version) packages.add(`${name}@${version}`);
154
+ if (tree.children) extractYarnDeps(tree.children);
155
+ });
156
+ };
157
+ extractYarnDeps(data.data.trees);
158
+ }
159
+ return packages;
160
+ } catch (e) {
161
+ log.warn("The 'yarn list' command failed. The project may have no dependencies installed.");
162
+ return new Set();
163
+ }
164
+ }
165
+
70
166
  // Modern format (npm 7+), 'packages' key
71
167
  if (lockfile.packages) {
72
- console.log(`🔍 Analyzing lockfile format v2/v3 (npm 7+)...`);
168
+ log.info(`Analyzing lockfile format v2/v3 (npm 7+)...`);
73
169
  for (const [pkgPath, details] of Object.entries(lockfile.packages)) {
74
170
  if (pkgPath && details.version) {
75
171
  // La ruta es como "node_modules/express" o "" para el root.
@@ -82,7 +178,7 @@ function getLocalPackages(lockfilePath) {
82
178
  }
83
179
  // Legacy format (npm 6), 'dependencies' key
84
180
  else if (lockfile.dependencies) {
85
- console.log(`🔍 Analyzing lockfile format v1 (npm 6)...`);
181
+ log.info(`Analyzing lockfile format v1 (npm 6)...`);
86
182
  const extractDeps = (deps) => {
87
183
  if (!deps) return;
88
184
  for (const [name, details] of Object.entries(deps)) {
@@ -104,22 +200,27 @@ function getLocalPackages(lockfilePath) {
104
200
  }
105
201
 
106
202
  /**
107
- * Recursively scans a directory for files matching a known malicious hash.
203
+ * Recursively finds all files in a directory, ignoring node_modules, .git, and binary-like extensions.
108
204
  * @param {string} directory - The directory to scan.
109
- * @returns {string[]} A list of paths to malicious files.
205
+ * @returns {string[]} A list of file paths.
110
206
  */
111
- function scanForMaliciousFiles(directory) {
112
- console.log(`${colors.CYAN}🔍 Scanning file signatures in ${directory}...${colors.RESET}`);
113
- const maliciousFiles = [];
207
+ function getAllFiles(directory) {
114
208
  const filesToScan = [];
209
+ const ignoredDirs = new Set(['node_modules', '.git']);
210
+ const ignoredExtensions = new Set(['.md', '.d.ts', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.woff', '.woff2', '.eot', '.ttf', '.ico']);
115
211
 
116
- // Get all files recursively, ignoring node_modules and .git
117
212
  function findFiles(dir) {
118
- const entries = fs.readdirSync(dir, { withFileTypes: true });
213
+ let entries;
214
+ try {
215
+ entries = fs.readdirSync(dir, { withFileTypes: true });
216
+ } catch (e) {
217
+ return; // Ignore permission errors
218
+ }
219
+
119
220
  for (const entry of entries) {
120
221
  const fullPath = path.join(dir, entry.name);
121
222
  if (entry.isDirectory()) {
122
- if (entry.name !== 'node_modules' && entry.name !== '.git') {
223
+ if (!ignoredDirs.has(entry.name)) {
123
224
  findFiles(fullPath);
124
225
  }
125
226
  } else if (entry.isFile()) {
@@ -130,19 +231,139 @@ function scanForMaliciousFiles(directory) {
130
231
 
131
232
  findFiles(directory);
132
233
 
133
- for (const file of filesToScan) {
134
- const fileBuffer = fs.readFileSync(file);
135
- const hashSum = crypto.createHash('sha256');
136
- hashSum.update(fileBuffer);
137
- const hex = hashSum.digest('hex');
234
+ return filesToScan.filter(file => !ignoredExtensions.has(path.extname(file)));
235
+ }
138
236
 
139
- if (hex === MALICIOUS_HASH) {
140
- maliciousFiles.push(path.relative(directory, file));
237
+ /**
238
+ * Scans a list of files for multiple types of threats.
239
+ * @param {string[]} allFiles - A list of absolute file paths to scan.
240
+ * @param {string} projectRoot - The root directory of the project for relative paths.
241
+ * @returns {object} An object containing arrays of different findings.
242
+ */
243
+ function scanProjectFiles(allFiles, projectRoot) {
244
+ log.info(`Scanning ${allFiles.length} project files for malicious indicators...`);
245
+
246
+ const findings = {
247
+ hashMatches: [],
248
+ namespaceMatches: new Set(),
249
+ hookMatches: [],
250
+ correlatedExfil: [],
251
+ };
252
+
253
+ const pkgJsonFiles = allFiles.filter(f => path.basename(f) === 'package.json');
254
+
255
+ // Scan for compromised namespaces and postinstall hooks in package.json files
256
+ log.info("Checking for compromised namespaces and package.json hooks...");
257
+ for (const file of pkgJsonFiles) {
258
+ try {
259
+ const content = fs.readFileSync(file, 'utf-8');
260
+ const pkg = JSON.parse(content);
261
+
262
+ // Check namespaces
263
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
264
+ for (const depName in allDeps) {
265
+ const namespace = depName.split('/')[0];
266
+ if (COMPROMISED_NAMESPACES.includes(namespace)) {
267
+ findings.namespaceMatches.add(`Warning: Contains packages from compromised namespace: ${namespace} (Found in ${path.relative(projectRoot, file)})`);
268
+ }
269
+ }
270
+
271
+ // Check for postinstall hooks
272
+ if (pkg.scripts && pkg.scripts.postinstall) {
273
+ findings.hookMatches.push(`File: ${path.relative(projectRoot, file)}`);
274
+ }
275
+ } catch (e) {
276
+ log.warn(`Could not parse ${path.relative(projectRoot, file)}: ${e.message}`);
141
277
  }
142
278
  }
143
- return maliciousFiles;
279
+
280
+ // Scan file contents for hashes and exfiltration patterns
281
+ log.info("Scanning file signatures and for exfiltration patterns...");
282
+ const envRegex = new RegExp(ENV_PATTERNS.join('|'));
283
+ const exfilRegex = new RegExp(EXFIL_PATTERNS.join('|'));
284
+
285
+ for (const file of allFiles) {
286
+ try {
287
+ const fileBuffer = fs.readFileSync(file);
288
+
289
+ // 1. Check hash
290
+ const hashSum = crypto.createHash('sha256');
291
+ hashSum.update(fileBuffer);
292
+ const hex = hashSum.digest('hex');
293
+
294
+ if (MALICIOUS_HASHES.has(hex)) {
295
+ findings.hashMatches.push(path.relative(projectRoot, file));
296
+ }
297
+
298
+ // 2. Check for correlated exfiltration (only for text files)
299
+ if (file.endsWith('.js') || file.endsWith('.ts') || file.endsWith('.json') || file.endsWith('.sh') || file.endsWith('.yml')) {
300
+ const content = fileBuffer.toString('utf-8');
301
+ const hasEnv = envRegex.test(content);
302
+ const hasExfil = exfilRegex.test(content);
303
+
304
+ if (hasEnv && hasExfil) {
305
+ findings.correlatedExfil.push(path.relative(projectRoot, file));
306
+ }
307
+ }
308
+ } catch (e) {
309
+ // Ignore errors for files that might be deleted during scan, etc.
310
+ }
311
+ }
312
+
313
+ log.info("File scanning complete.");
314
+ return findings;
144
315
  }
145
316
 
317
+ /**
318
+ * Orchestrates the dependency analysis.
319
+ * @param {string} projectRoot The root of the project.
320
+ * @returns {Promise<Set<string>>} A set of matched compromised dependencies.
321
+ */
322
+ async function runDependencyAnalysis(projectRoot) {
323
+ log.header("Module 1: Dependency Analysis");
324
+ const pnpmLockFile = path.join(projectRoot, 'pnpm-lock.yaml');
325
+ const yarnLockFile = path.join(projectRoot, 'yarn.lock');
326
+ const npmLockFile = path.join(projectRoot, 'package-lock.json');
327
+ const pkgFile = path.join(projectRoot, 'package.json');
328
+
329
+ if (!fs.existsSync(pkgFile)) {
330
+ log.warn("No package.json found. Skipping all dependency analysis.");
331
+ return new Set();
332
+ }
333
+
334
+ let localPackages = new Set();
335
+ if (fs.existsSync(pnpmLockFile) && commandExists('pnpm')) {
336
+ localPackages = parsePnpmLock(projectRoot);
337
+ } else if (fs.existsSync(yarnLockFile) && commandExists('yarn')) {
338
+ // Simple check for Yarn v1. Modern yarn doesn't use `yarn list` in the same way.
339
+ const yarnVersion = execSync('yarn --version', { encoding: 'utf8' });
340
+ if (yarnVersion.startsWith('1.')) {
341
+ localPackages = getLocalPackages(yarnLockFile);
342
+ } else {
343
+ log.warn("Modern Yarn (v2+) detected. This script's dependency analysis for Yarn currently only supports v1. Skipping.");
344
+ }
345
+ } else if (fs.existsSync(npmLockFile)) {
346
+ localPackages = getLocalPackages(npmLockFile);
347
+ } else {
348
+ localPackages = parsePackageJson(pkgFile);
349
+ }
350
+
351
+ if (localPackages.size === 0) {
352
+ log.warn("Could not determine local packages. Skipping version check.");
353
+ return new Set();
354
+ }
355
+
356
+ log.info("Checking for vulnerable versions...");
357
+ const compromisedPackages = new Set(await getCompromisedPackages());
358
+ const matches = new Set();
359
+ for (const localPkg of localPackages) {
360
+ if (compromisedPackages.has(localPkg)) {
361
+ matches.add(localPkg);
362
+ }
363
+ }
364
+ log.info("Dependency analysis complete.");
365
+ return matches;
366
+ }
146
367
  /**
147
368
  * Main function to orchestrate the scan.
148
369
  */
@@ -156,60 +377,76 @@ async function main() {
156
377
  throw new Error(`Project directory not found: ${projectRoot}`);
157
378
  }
158
379
 
159
- console.log(`\n${colors.BLUE}--- Shai-Hulud Integrity Scanner (Node.js) ---${colors.RESET}`);
160
- console.log(`Scanning project at: ${projectRoot}`);
380
+ console.log(`\n${colors.BLUE}${colors.BOLD}--- Shai-Hulud Integrity Scanner (Node.js) ---${colors.RESET}`);
381
+ log.info(`Scanning project at: ${projectRoot}`);
161
382
 
162
- // --- Vector 1: Dependency Analysis ---
163
- let dependencyMatches = new Set();
164
- const lockfilePath = path.join(projectRoot, 'package-lock.json');
383
+ // --- Run Analyses ---
384
+ const dependencyMatches = await runDependencyAnalysis(projectRoot);
165
385
 
166
- if (fs.existsSync(lockfilePath)) {
167
- const localPackages = getLocalPackages(lockfilePath);
168
- const compromisedPackages = await getCompromisedPackages();
169
- console.log(`${colors.CYAN}🔍 Comparing dependencies...${colors.RESET}`);
170
- for (const localPkg of localPackages) {
171
- if (compromisedPackages.has(localPkg)) {
172
- dependencyMatches.add(localPkg);
173
- }
174
- }
175
- } else {
176
- console.log(`${colors.YELLOW}⚠️ No package-lock.json found. Skipping dependency scan.${colors.RESET}`);
177
- }
178
-
179
- // --- Vector 2: File Signature Analysis ---
180
- const fileMatches = scanForMaliciousFiles(projectRoot);
386
+ log.header("Module 2: Project Structure & Content Analysis");
387
+ const allFiles = getAllFiles(projectRoot);
388
+ const fileScanFindings = scanProjectFiles(allFiles, projectRoot);
181
389
 
182
390
  // --- Reporting ---
183
- console.log('\n' + '-'.repeat(50));
391
+ log.header("Scan Report");
184
392
  let issuesFound = false;
393
+ let report = "";
185
394
 
186
- if (fileMatches.length > 0) {
395
+ if (fileScanFindings.hashMatches.length > 0) {
187
396
  issuesFound = true;
188
- console.log(`${colors.RED}🚨 CRITICAL RISK: Known Malware Signature Detected${colors.RESET}`);
189
- fileMatches.forEach(match => {
190
- console.log(` - File: ${colors.YELLOW}${match}${colors.RESET}`);
397
+ report += `${colors.RED}🚨 CRITICAL RISK: Known Malware Signature Detected${colors.RESET}\n`;
398
+ fileScanFindings.hashMatches.forEach(match => {
399
+ report += ` - File with matching signature: ${colors.YELLOW}${match}${colors.RESET}\n`;
191
400
  });
192
- console.log('');
401
+ report += " NOTE: This is a definitive indicator of compromise. Immediate investigation is required.\n\n";
402
+ }
403
+
404
+ if (fileScanFindings.correlatedExfil.length > 0) {
405
+ issuesFound = true;
406
+ report += `${colors.RED}🚨 HIGH RISK: Environment Scanning with Exfiltration Detected${colors.RESET}\n`;
407
+ fileScanFindings.correlatedExfil.forEach(match => {
408
+ report += ` - File: ${colors.YELLOW}${match}${colors.RESET}\n`;
409
+ });
410
+ report += " NOTE: These files access secrets AND contain data exfiltration patterns.\n\n";
193
411
  }
194
412
 
195
413
  if (dependencyMatches.size > 0) {
196
414
  issuesFound = true;
197
- console.log(`${colors.RED}🚨 HIGH RISK: Compromised Package Versions Detected${colors.RESET}`);
415
+ report += `${colors.RED}🚨 HIGH RISK: Compromised Package Versions Detected${colors.RESET}\n`;
198
416
  dependencyMatches.forEach(match => {
199
- console.log(` - ${colors.YELLOW}${match}${colors.RESET}`);
417
+ report += ` - Package: ${colors.YELLOW}${match}${colors.RESET}\n`;
418
+ });
419
+ report += " NOTE: These specific package versions are known to be compromised.\n\n";
420
+ }
421
+
422
+ if (fileScanFindings.namespaceMatches.size > 0) {
423
+ issuesFound = true;
424
+ report += `${colors.YELLOW}⚠️ MEDIUM RISK: Packages from Compromised Namespaces${colors.RESET}\n`;
425
+ fileScanFindings.namespaceMatches.forEach(match => {
426
+ report += ` - ${match}\n`;
427
+ });
428
+ report += " NOTE: Review packages from these organizations carefully.\n\n";
429
+ }
430
+
431
+ if (fileScanFindings.hookMatches.length > 0) {
432
+ issuesFound = true;
433
+ report += `${colors.YELLOW}⚠️ MEDIUM RISK: Potentially Malicious package.json Hooks${colors.RESET}\n`;
434
+ fileScanFindings.hookMatches.forEach(match => {
435
+ report += ` - ${match}\n`;
200
436
  });
437
+ report += " NOTE: 'postinstall' scripts can execute arbitrary commands and require review.\n\n";
201
438
  }
202
439
 
203
- console.log('-'.repeat(50));
204
440
  if (issuesFound) {
205
- console.log(`${colors.RED}Scan complete. Actionable issues were found.${colors.RESET}`);
441
+ console.log(report);
442
+ log.error("Scan complete. Actionable issues were found.");
206
443
  process.exit(2);
207
444
  } else {
208
- console.log(`${colors.GREEN}✅ All good! No known integrity issues found.${colors.RESET}`);
445
+ log.info(`${colors.GREEN}✅ No actionable project integrity issues found.${colors.RESET}`);
209
446
  }
210
447
 
211
448
  } catch (error) {
212
- console.error(`${colors.RED}❌ ERROR: ${error.message}${colors.RESET}`);
449
+ log.error(error.message);
213
450
  process.exit(1);
214
451
  }
215
452
  }