hulud-party-scanner 1.0.1 → 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 +13 -1
- package/compromised-libs.txt +180 -204
- package/package.json +1 -1
- package/scan.js +305 -80
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 [
|
|
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.
|
package/compromised-libs.txt
CHANGED
|
@@ -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
|
-
|
|
87
|
-
@
|
|
88
|
-
@
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
package/scan.js
CHANGED
|
@@ -3,27 +3,67 @@ 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/
|
|
9
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
205
|
+
* @returns {string[]} A list of file paths.
|
|
110
206
|
*/
|
|
111
|
-
function
|
|
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
|
-
|
|
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 (
|
|
223
|
+
if (!ignoredDirs.has(entry.name)) {
|
|
123
224
|
findFiles(fullPath);
|
|
124
225
|
}
|
|
125
226
|
} else if (entry.isFile()) {
|
|
@@ -130,98 +231,222 @@ function scanForMaliciousFiles(directory) {
|
|
|
130
231
|
|
|
131
232
|
findFiles(directory);
|
|
132
233
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
234
|
+
return filesToScan.filter(file => !ignoredExtensions.has(path.extname(file)));
|
|
235
|
+
}
|
|
236
|
+
|
|
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');
|
|
138
254
|
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
*/
|
|
149
370
|
async function main() {
|
|
150
371
|
try {
|
|
151
372
|
// Use the provided path or the current directory.
|
|
152
|
-
|
|
153
|
-
const args = process.argv.slice(2);
|
|
154
|
-
const doubleDashIndex = args.indexOf('--');
|
|
155
|
-
|
|
156
|
-
let targetArg;
|
|
157
|
-
if (doubleDashIndex !== -1) {
|
|
158
|
-
// If '--' is present, the path is the argument right after it.
|
|
159
|
-
targetArg = args[doubleDashIndex + 1];
|
|
160
|
-
} else {
|
|
161
|
-
// Otherwise, it's the first argument.
|
|
162
|
-
targetArg = args[0];
|
|
163
|
-
}
|
|
164
|
-
const targetPath = targetArg || '.';
|
|
373
|
+
const targetPath = process.argv[2] || '.';
|
|
165
374
|
const projectRoot = path.resolve(targetPath);
|
|
166
375
|
|
|
167
376
|
if (!fs.existsSync(projectRoot) || !fs.statSync(projectRoot).isDirectory()) {
|
|
168
377
|
throw new Error(`Project directory not found: ${projectRoot}`);
|
|
169
378
|
}
|
|
170
379
|
|
|
171
|
-
console.log(`\n${colors.BLUE}--- Shai-Hulud Integrity Scanner (Node.js) ---${colors.RESET}`);
|
|
172
|
-
|
|
380
|
+
console.log(`\n${colors.BLUE}${colors.BOLD}--- Shai-Hulud Integrity Scanner (Node.js) ---${colors.RESET}`);
|
|
381
|
+
log.info(`Scanning project at: ${projectRoot}`);
|
|
173
382
|
|
|
174
|
-
// ---
|
|
175
|
-
|
|
176
|
-
const lockfilePath = path.join(projectRoot, 'package-lock.json');
|
|
383
|
+
// --- Run Analyses ---
|
|
384
|
+
const dependencyMatches = await runDependencyAnalysis(projectRoot);
|
|
177
385
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
console.log(`${colors.CYAN}🔍 Comparing dependencies...${colors.RESET}`);
|
|
182
|
-
for (const localPkg of localPackages) {
|
|
183
|
-
if (compromisedPackages.has(localPkg)) {
|
|
184
|
-
dependencyMatches.add(localPkg);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
console.log(`${colors.YELLOW}⚠️ No package-lock.json found. Skipping dependency scan.${colors.RESET}`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// --- Vector 2: File Signature Analysis ---
|
|
192
|
-
const fileMatches = scanForMaliciousFiles(projectRoot);
|
|
386
|
+
log.header("Module 2: Project Structure & Content Analysis");
|
|
387
|
+
const allFiles = getAllFiles(projectRoot);
|
|
388
|
+
const fileScanFindings = scanProjectFiles(allFiles, projectRoot);
|
|
193
389
|
|
|
194
390
|
// --- Reporting ---
|
|
195
|
-
|
|
391
|
+
log.header("Scan Report");
|
|
196
392
|
let issuesFound = false;
|
|
393
|
+
let report = "";
|
|
197
394
|
|
|
198
|
-
if (
|
|
395
|
+
if (fileScanFindings.hashMatches.length > 0) {
|
|
199
396
|
issuesFound = true;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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`;
|
|
203
400
|
});
|
|
204
|
-
|
|
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";
|
|
205
411
|
}
|
|
206
412
|
|
|
207
413
|
if (dependencyMatches.size > 0) {
|
|
208
414
|
issuesFound = true;
|
|
209
|
-
|
|
415
|
+
report += `${colors.RED}🚨 HIGH RISK: Compromised Package Versions Detected${colors.RESET}\n`;
|
|
210
416
|
dependencyMatches.forEach(match => {
|
|
211
|
-
|
|
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`;
|
|
212
436
|
});
|
|
437
|
+
report += " NOTE: 'postinstall' scripts can execute arbitrary commands and require review.\n\n";
|
|
213
438
|
}
|
|
214
439
|
|
|
215
|
-
console.log('-'.repeat(50));
|
|
216
440
|
if (issuesFound) {
|
|
217
|
-
console.log(
|
|
441
|
+
console.log(report);
|
|
442
|
+
log.error("Scan complete. Actionable issues were found.");
|
|
218
443
|
process.exit(2);
|
|
219
444
|
} else {
|
|
220
|
-
|
|
445
|
+
log.info(`${colors.GREEN}✅ No actionable project integrity issues found.${colors.RESET}`);
|
|
221
446
|
}
|
|
222
447
|
|
|
223
448
|
} catch (error) {
|
|
224
|
-
|
|
449
|
+
log.error(error.message);
|
|
225
450
|
process.exit(1);
|
|
226
451
|
}
|
|
227
452
|
}
|