flagrix 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "
|
|
3
|
-
"lastUpdated": "2026-07-
|
|
2
|
+
"version": "2026.07.08.002",
|
|
3
|
+
"lastUpdated": "2026-07-08T16:18:50.866Z",
|
|
4
4
|
"maliciousPackages": [
|
|
5
5
|
{
|
|
6
6
|
"name": "beavertail",
|
|
@@ -130,7 +130,13 @@
|
|
|
130
130
|
"loader",
|
|
131
131
|
"apt"
|
|
132
132
|
],
|
|
133
|
-
"severity": "critical"
|
|
133
|
+
"severity": "critical",
|
|
134
|
+
"fileExtensions": [
|
|
135
|
+
".js",
|
|
136
|
+
".ts",
|
|
137
|
+
".mjs",
|
|
138
|
+
".cjs"
|
|
139
|
+
]
|
|
134
140
|
},
|
|
135
141
|
{
|
|
136
142
|
"id": "BEAVERTAIL_EXFIL",
|
|
@@ -143,7 +149,13 @@
|
|
|
143
149
|
"exfiltration",
|
|
144
150
|
"apt"
|
|
145
151
|
],
|
|
146
|
-
"severity": "critical"
|
|
152
|
+
"severity": "critical",
|
|
153
|
+
"fileExtensions": [
|
|
154
|
+
".js",
|
|
155
|
+
".ts",
|
|
156
|
+
".mjs",
|
|
157
|
+
".cjs"
|
|
158
|
+
]
|
|
147
159
|
},
|
|
148
160
|
{
|
|
149
161
|
"id": "INVISIBLEFERRET_KEYLOG",
|
|
@@ -156,7 +168,10 @@
|
|
|
156
168
|
"keylogger",
|
|
157
169
|
"apt"
|
|
158
170
|
],
|
|
159
|
-
"severity": "critical"
|
|
171
|
+
"severity": "critical",
|
|
172
|
+
"fileExtensions": [
|
|
173
|
+
".py"
|
|
174
|
+
]
|
|
160
175
|
},
|
|
161
176
|
{
|
|
162
177
|
"id": "REVERSE_SHELL",
|
|
@@ -168,7 +183,12 @@
|
|
|
168
183
|
"backdoor",
|
|
169
184
|
"remote-access"
|
|
170
185
|
],
|
|
171
|
-
"severity": "critical"
|
|
186
|
+
"severity": "critical",
|
|
187
|
+
"fileExtensions": [
|
|
188
|
+
".js",
|
|
189
|
+
".ts",
|
|
190
|
+
".py"
|
|
191
|
+
]
|
|
172
192
|
},
|
|
173
193
|
{
|
|
174
194
|
"id": "CRYPTO_MINER",
|
|
@@ -180,7 +200,13 @@
|
|
|
180
200
|
"crypto",
|
|
181
201
|
"resource-abuse"
|
|
182
202
|
],
|
|
183
|
-
"severity": "
|
|
203
|
+
"severity": "critical",
|
|
204
|
+
"fileExtensions": [
|
|
205
|
+
".js",
|
|
206
|
+
".ts",
|
|
207
|
+
".py",
|
|
208
|
+
".sh"
|
|
209
|
+
]
|
|
184
210
|
},
|
|
185
211
|
{
|
|
186
212
|
"id": "CREDENTIAL_THEFT",
|
|
@@ -192,7 +218,13 @@
|
|
|
192
218
|
"theft",
|
|
193
219
|
"stealer"
|
|
194
220
|
],
|
|
195
|
-
"severity": "critical"
|
|
221
|
+
"severity": "critical",
|
|
222
|
+
"fileExtensions": [
|
|
223
|
+
".js",
|
|
224
|
+
".ts",
|
|
225
|
+
".py",
|
|
226
|
+
".sh"
|
|
227
|
+
]
|
|
196
228
|
},
|
|
197
229
|
{
|
|
198
230
|
"id": "ENV_EXFIL_WEBHOOK",
|
|
@@ -204,7 +236,12 @@
|
|
|
204
236
|
"webhook",
|
|
205
237
|
"secrets"
|
|
206
238
|
],
|
|
207
|
-
"severity": "high"
|
|
239
|
+
"severity": "high",
|
|
240
|
+
"fileExtensions": [
|
|
241
|
+
".js",
|
|
242
|
+
".ts",
|
|
243
|
+
".py"
|
|
244
|
+
]
|
|
208
245
|
},
|
|
209
246
|
{
|
|
210
247
|
"id": "DISCORD_WEBHOOK_EXFIL",
|
|
@@ -216,7 +253,12 @@
|
|
|
216
253
|
"discord",
|
|
217
254
|
"webhook"
|
|
218
255
|
],
|
|
219
|
-
"severity": "high"
|
|
256
|
+
"severity": "high",
|
|
257
|
+
"fileExtensions": [
|
|
258
|
+
".js",
|
|
259
|
+
".ts",
|
|
260
|
+
".py"
|
|
261
|
+
]
|
|
220
262
|
},
|
|
221
263
|
{
|
|
222
264
|
"id": "OBFUSCATED_EVAL",
|
|
@@ -228,19 +270,12 @@
|
|
|
228
270
|
"eval",
|
|
229
271
|
"payload"
|
|
230
272
|
],
|
|
231
|
-
"severity": "high"
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
"description": "eval(), new Function(), or setTimeout/setInterval with string argument — executes arbitrary dynamic code",
|
|
238
|
-
"tags": [
|
|
239
|
-
"obfuscation",
|
|
240
|
-
"eval",
|
|
241
|
-
"dynamic-code"
|
|
242
|
-
],
|
|
243
|
-
"severity": "high"
|
|
273
|
+
"severity": "high",
|
|
274
|
+
"fileExtensions": [
|
|
275
|
+
".js",
|
|
276
|
+
".ts",
|
|
277
|
+
".mjs"
|
|
278
|
+
]
|
|
244
279
|
},
|
|
245
280
|
{
|
|
246
281
|
"id": "HARDCODED_AWS_KEY",
|
|
@@ -252,7 +287,17 @@
|
|
|
252
287
|
"aws",
|
|
253
288
|
"credentials"
|
|
254
289
|
],
|
|
255
|
-
"severity": "critical"
|
|
290
|
+
"severity": "critical",
|
|
291
|
+
"fileExtensions": [
|
|
292
|
+
".js",
|
|
293
|
+
".ts",
|
|
294
|
+
".py",
|
|
295
|
+
".sh",
|
|
296
|
+
".env",
|
|
297
|
+
".json",
|
|
298
|
+
".yaml",
|
|
299
|
+
".yml"
|
|
300
|
+
]
|
|
256
301
|
},
|
|
257
302
|
{
|
|
258
303
|
"id": "HARDCODED_GITHUB_TOKEN",
|
|
@@ -264,7 +309,15 @@
|
|
|
264
309
|
"github",
|
|
265
310
|
"credentials"
|
|
266
311
|
],
|
|
267
|
-
"severity": "critical"
|
|
312
|
+
"severity": "critical",
|
|
313
|
+
"fileExtensions": [
|
|
314
|
+
".js",
|
|
315
|
+
".ts",
|
|
316
|
+
".py",
|
|
317
|
+
".sh",
|
|
318
|
+
".yaml",
|
|
319
|
+
".yml"
|
|
320
|
+
]
|
|
268
321
|
},
|
|
269
322
|
{
|
|
270
323
|
"id": "HARDCODED_STRIPE_KEY",
|
|
@@ -276,7 +329,12 @@
|
|
|
276
329
|
"stripe",
|
|
277
330
|
"credentials"
|
|
278
331
|
],
|
|
279
|
-
"severity": "critical"
|
|
332
|
+
"severity": "critical",
|
|
333
|
+
"fileExtensions": [
|
|
334
|
+
".js",
|
|
335
|
+
".ts",
|
|
336
|
+
".py"
|
|
337
|
+
]
|
|
280
338
|
},
|
|
281
339
|
{
|
|
282
340
|
"id": "HARDCODED_API_KEY",
|
|
@@ -288,7 +346,12 @@
|
|
|
288
346
|
"credentials",
|
|
289
347
|
"api-key"
|
|
290
348
|
],
|
|
291
|
-
"severity": "critical"
|
|
349
|
+
"severity": "critical",
|
|
350
|
+
"fileExtensions": [
|
|
351
|
+
".js",
|
|
352
|
+
".ts",
|
|
353
|
+
".py"
|
|
354
|
+
]
|
|
292
355
|
},
|
|
293
356
|
{
|
|
294
357
|
"id": "HARDCODED_DB_CONNECTION",
|
|
@@ -300,7 +363,13 @@
|
|
|
300
363
|
"database",
|
|
301
364
|
"credentials"
|
|
302
365
|
],
|
|
303
|
-
"severity": "high"
|
|
366
|
+
"severity": "high",
|
|
367
|
+
"fileExtensions": [
|
|
368
|
+
".js",
|
|
369
|
+
".ts",
|
|
370
|
+
".py",
|
|
371
|
+
".env"
|
|
372
|
+
]
|
|
304
373
|
},
|
|
305
374
|
{
|
|
306
375
|
"id": "NETWORK_DISCORD_WEBHOOK",
|
|
@@ -312,7 +381,13 @@
|
|
|
312
381
|
"discord",
|
|
313
382
|
"exfiltration"
|
|
314
383
|
],
|
|
315
|
-
"severity": "critical"
|
|
384
|
+
"severity": "critical",
|
|
385
|
+
"fileExtensions": [
|
|
386
|
+
".js",
|
|
387
|
+
".ts",
|
|
388
|
+
".py",
|
|
389
|
+
".sh"
|
|
390
|
+
]
|
|
316
391
|
},
|
|
317
392
|
{
|
|
318
393
|
"id": "NETWORK_TELEGRAM_TOKEN",
|
|
@@ -324,7 +399,13 @@
|
|
|
324
399
|
"telegram",
|
|
325
400
|
"c2"
|
|
326
401
|
],
|
|
327
|
-
"severity": "high"
|
|
402
|
+
"severity": "high",
|
|
403
|
+
"fileExtensions": [
|
|
404
|
+
".js",
|
|
405
|
+
".ts",
|
|
406
|
+
".py",
|
|
407
|
+
".sh"
|
|
408
|
+
]
|
|
328
409
|
},
|
|
329
410
|
{
|
|
330
411
|
"id": "NETWORK_PASTEBIN",
|
|
@@ -336,7 +417,13 @@
|
|
|
336
417
|
"pastebin",
|
|
337
418
|
"payload-hosting"
|
|
338
419
|
],
|
|
339
|
-
"severity": "medium"
|
|
420
|
+
"severity": "medium",
|
|
421
|
+
"fileExtensions": [
|
|
422
|
+
".js",
|
|
423
|
+
".ts",
|
|
424
|
+
".py",
|
|
425
|
+
".sh"
|
|
426
|
+
]
|
|
340
427
|
},
|
|
341
428
|
{
|
|
342
429
|
"id": "NETWORK_URL_SHORTENER",
|
|
@@ -348,7 +435,13 @@
|
|
|
348
435
|
"url-shortener",
|
|
349
436
|
"obfuscation"
|
|
350
437
|
],
|
|
351
|
-
"severity": "medium"
|
|
438
|
+
"severity": "medium",
|
|
439
|
+
"fileExtensions": [
|
|
440
|
+
".js",
|
|
441
|
+
".ts",
|
|
442
|
+
".py",
|
|
443
|
+
".sh"
|
|
444
|
+
]
|
|
352
445
|
},
|
|
353
446
|
{
|
|
354
447
|
"id": "NETWORK_SUSPICIOUS_TLD",
|
|
@@ -360,7 +453,13 @@
|
|
|
360
453
|
"suspicious-domain",
|
|
361
454
|
"malware-hosting"
|
|
362
455
|
],
|
|
363
|
-
"severity": "high"
|
|
456
|
+
"severity": "high",
|
|
457
|
+
"fileExtensions": [
|
|
458
|
+
".js",
|
|
459
|
+
".ts",
|
|
460
|
+
".py",
|
|
461
|
+
".sh"
|
|
462
|
+
]
|
|
364
463
|
},
|
|
365
464
|
{
|
|
366
465
|
"id": "NETWORK_NGROK_TUNNEL",
|
|
@@ -372,7 +471,13 @@
|
|
|
372
471
|
"ngrok",
|
|
373
472
|
"c2"
|
|
374
473
|
],
|
|
375
|
-
"severity": "medium"
|
|
474
|
+
"severity": "medium",
|
|
475
|
+
"fileExtensions": [
|
|
476
|
+
".js",
|
|
477
|
+
".ts",
|
|
478
|
+
".py",
|
|
479
|
+
".sh"
|
|
480
|
+
]
|
|
376
481
|
},
|
|
377
482
|
{
|
|
378
483
|
"id": "EXFIL_CLIPBOARD",
|
|
@@ -384,7 +489,11 @@
|
|
|
384
489
|
"clipboard",
|
|
385
490
|
"data-theft"
|
|
386
491
|
],
|
|
387
|
-
"severity": "high"
|
|
492
|
+
"severity": "high",
|
|
493
|
+
"fileExtensions": [
|
|
494
|
+
".js",
|
|
495
|
+
".ts"
|
|
496
|
+
]
|
|
388
497
|
},
|
|
389
498
|
{
|
|
390
499
|
"id": "EXFIL_COOKIE",
|
|
@@ -396,7 +505,11 @@
|
|
|
396
505
|
"cookies",
|
|
397
506
|
"session-theft"
|
|
398
507
|
],
|
|
399
|
-
"severity": "high"
|
|
508
|
+
"severity": "high",
|
|
509
|
+
"fileExtensions": [
|
|
510
|
+
".js",
|
|
511
|
+
".ts"
|
|
512
|
+
]
|
|
400
513
|
},
|
|
401
514
|
{
|
|
402
515
|
"id": "EXFIL_KEYLOGGER",
|
|
@@ -408,7 +521,11 @@
|
|
|
408
521
|
"keylogger",
|
|
409
522
|
"credential-theft"
|
|
410
523
|
],
|
|
411
|
-
"severity": "critical"
|
|
524
|
+
"severity": "critical",
|
|
525
|
+
"fileExtensions": [
|
|
526
|
+
".js",
|
|
527
|
+
".ts"
|
|
528
|
+
]
|
|
412
529
|
},
|
|
413
530
|
{
|
|
414
531
|
"id": "BACKDOOR_RCE_ENDPOINT",
|
|
@@ -420,7 +537,12 @@
|
|
|
420
537
|
"rce",
|
|
421
538
|
"critical"
|
|
422
539
|
],
|
|
423
|
-
"severity": "critical"
|
|
540
|
+
"severity": "critical",
|
|
541
|
+
"fileExtensions": [
|
|
542
|
+
".js",
|
|
543
|
+
".ts",
|
|
544
|
+
".py"
|
|
545
|
+
]
|
|
424
546
|
},
|
|
425
547
|
{
|
|
426
548
|
"id": "BACKDOOR_DYNAMIC_REQUIRE",
|
|
@@ -432,7 +554,11 @@
|
|
|
432
554
|
"rce",
|
|
433
555
|
"dynamic-require"
|
|
434
556
|
],
|
|
435
|
-
"severity": "critical"
|
|
557
|
+
"severity": "critical",
|
|
558
|
+
"fileExtensions": [
|
|
559
|
+
".js",
|
|
560
|
+
".ts"
|
|
561
|
+
]
|
|
436
562
|
},
|
|
437
563
|
{
|
|
438
564
|
"id": "BACKDOOR_HARDCODED_AUTH",
|
|
@@ -444,7 +570,12 @@
|
|
|
444
570
|
"auth-bypass",
|
|
445
571
|
"hardcoded"
|
|
446
572
|
],
|
|
447
|
-
"severity": "critical"
|
|
573
|
+
"severity": "critical",
|
|
574
|
+
"fileExtensions": [
|
|
575
|
+
".js",
|
|
576
|
+
".ts",
|
|
577
|
+
".py"
|
|
578
|
+
]
|
|
448
579
|
},
|
|
449
580
|
{
|
|
450
581
|
"id": "FILE_ACCESS_CREDENTIALS",
|
|
@@ -457,7 +588,12 @@
|
|
|
457
588
|
"ssh",
|
|
458
589
|
"aws"
|
|
459
590
|
],
|
|
460
|
-
"severity": "critical"
|
|
591
|
+
"severity": "critical",
|
|
592
|
+
"fileExtensions": [
|
|
593
|
+
".js",
|
|
594
|
+
".ts",
|
|
595
|
+
".py"
|
|
596
|
+
]
|
|
461
597
|
},
|
|
462
598
|
{
|
|
463
599
|
"id": "FILE_ACCESS_SYSTEM",
|
|
@@ -469,7 +605,12 @@
|
|
|
469
605
|
"system-files",
|
|
470
606
|
"privilege-escalation"
|
|
471
607
|
],
|
|
472
|
-
"severity": "critical"
|
|
608
|
+
"severity": "critical",
|
|
609
|
+
"fileExtensions": [
|
|
610
|
+
".js",
|
|
611
|
+
".ts",
|
|
612
|
+
".py"
|
|
613
|
+
]
|
|
473
614
|
},
|
|
474
615
|
{
|
|
475
616
|
"id": "FILE_ACCESS_BROWSER_DATA",
|
|
@@ -481,7 +622,12 @@
|
|
|
481
622
|
"browser-data",
|
|
482
623
|
"credential-theft"
|
|
483
624
|
],
|
|
484
|
-
"severity": "critical"
|
|
625
|
+
"severity": "critical",
|
|
626
|
+
"fileExtensions": [
|
|
627
|
+
".js",
|
|
628
|
+
".ts",
|
|
629
|
+
".py"
|
|
630
|
+
]
|
|
485
631
|
},
|
|
486
632
|
{
|
|
487
633
|
"id": "SOCIAL_ENGINEERING_SECURITY_BYPASS",
|
|
@@ -503,7 +649,15 @@
|
|
|
503
649
|
"base64",
|
|
504
650
|
"obfuscation"
|
|
505
651
|
],
|
|
506
|
-
"severity": "medium"
|
|
652
|
+
"severity": "medium",
|
|
653
|
+
"minMatches": 6,
|
|
654
|
+
"fileExtensions": [
|
|
655
|
+
".js",
|
|
656
|
+
".ts",
|
|
657
|
+
".py",
|
|
658
|
+
".sh",
|
|
659
|
+
".ps1"
|
|
660
|
+
]
|
|
507
661
|
},
|
|
508
662
|
{
|
|
509
663
|
"id": "OBF_HEX_STRINGS",
|
|
@@ -514,7 +668,13 @@
|
|
|
514
668
|
"hex-encoding",
|
|
515
669
|
"obfuscation"
|
|
516
670
|
],
|
|
517
|
-
"severity": "medium"
|
|
671
|
+
"severity": "medium",
|
|
672
|
+
"minMatches": 20,
|
|
673
|
+
"fileExtensions": [
|
|
674
|
+
".js",
|
|
675
|
+
".ts",
|
|
676
|
+
".py"
|
|
677
|
+
]
|
|
518
678
|
},
|
|
519
679
|
{
|
|
520
680
|
"id": "OBF_EVAL",
|
|
@@ -526,7 +686,12 @@
|
|
|
526
686
|
"dynamic-code",
|
|
527
687
|
"obfuscation"
|
|
528
688
|
],
|
|
529
|
-
"severity": "high"
|
|
689
|
+
"severity": "high",
|
|
690
|
+
"fileExtensions": [
|
|
691
|
+
".js",
|
|
692
|
+
".ts",
|
|
693
|
+
".mjs"
|
|
694
|
+
]
|
|
530
695
|
},
|
|
531
696
|
{
|
|
532
697
|
"id": "OBF_NEW_FUNCTION",
|
|
@@ -538,7 +703,11 @@
|
|
|
538
703
|
"dynamic-code",
|
|
539
704
|
"obfuscation"
|
|
540
705
|
],
|
|
541
|
-
"severity": "high"
|
|
706
|
+
"severity": "high",
|
|
707
|
+
"fileExtensions": [
|
|
708
|
+
".js",
|
|
709
|
+
".ts"
|
|
710
|
+
]
|
|
542
711
|
},
|
|
543
712
|
{
|
|
544
713
|
"id": "OBF_SETTIMEOUT_STRING",
|
|
@@ -550,7 +719,11 @@
|
|
|
550
719
|
"dynamic-code",
|
|
551
720
|
"obfuscation"
|
|
552
721
|
],
|
|
553
|
-
"severity": "high"
|
|
722
|
+
"severity": "high",
|
|
723
|
+
"fileExtensions": [
|
|
724
|
+
".js",
|
|
725
|
+
".ts"
|
|
726
|
+
]
|
|
554
727
|
},
|
|
555
728
|
{
|
|
556
729
|
"id": "OBF_FROMCHARCODE",
|
|
@@ -562,7 +735,11 @@
|
|
|
562
735
|
"obfuscation",
|
|
563
736
|
"eval-bypass"
|
|
564
737
|
],
|
|
565
|
-
"severity": "medium"
|
|
738
|
+
"severity": "medium",
|
|
739
|
+
"fileExtensions": [
|
|
740
|
+
".js",
|
|
741
|
+
".ts"
|
|
742
|
+
]
|
|
566
743
|
},
|
|
567
744
|
{
|
|
568
745
|
"id": "OBF_CHARCODE_ARRAY",
|
|
@@ -573,7 +750,11 @@
|
|
|
573
750
|
"charcode-array",
|
|
574
751
|
"obfuscation"
|
|
575
752
|
],
|
|
576
|
-
"severity": "medium"
|
|
753
|
+
"severity": "medium",
|
|
754
|
+
"fileExtensions": [
|
|
755
|
+
".js",
|
|
756
|
+
".ts"
|
|
757
|
+
]
|
|
577
758
|
}
|
|
578
759
|
],
|
|
579
760
|
"knownBadHashes": [],
|
|
@@ -361,6 +361,91 @@ function collectEvidence(content, patterns, max = MAX_EVIDENCE_LINES) {
|
|
|
361
361
|
return evidence;
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
// node_modules/@flagrix/scanner-core/dist/utils/mask.js
|
|
365
|
+
var REGEX_ALLOWED_AFTER_CHAR = /* @__PURE__ */ new Set([..."=(:,[!&|?{};+*%^<>~"]);
|
|
366
|
+
var REGEX_ALLOWED_AFTER_WORD = /* @__PURE__ */ new Set(["return", "case", "typeof", "in", "of", "do", "void", "delete"]);
|
|
367
|
+
function maskRegexLiterals(content) {
|
|
368
|
+
return content.split("\n").map(maskLine).join("\n");
|
|
369
|
+
}
|
|
370
|
+
function maskLine(line) {
|
|
371
|
+
let out = "";
|
|
372
|
+
let i = 0;
|
|
373
|
+
while (i < line.length) {
|
|
374
|
+
const ch = line[i];
|
|
375
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
376
|
+
const end = stringEnd(line, i);
|
|
377
|
+
out += line.slice(i, end);
|
|
378
|
+
i = end;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (ch === "/" && line[i + 1] === "/") {
|
|
382
|
+
out += line.slice(i);
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
if (ch === "/" && regexCanStartAfter(out)) {
|
|
386
|
+
const end = regexEnd(line, i);
|
|
387
|
+
if (end !== -1) {
|
|
388
|
+
out += " ".repeat(end - i);
|
|
389
|
+
i = end;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
out += ch;
|
|
394
|
+
i++;
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
398
|
+
function regexCanStartAfter(before) {
|
|
399
|
+
const trimmed = before.trimEnd();
|
|
400
|
+
if (trimmed === "")
|
|
401
|
+
return true;
|
|
402
|
+
if (REGEX_ALLOWED_AFTER_CHAR.has(trimmed.slice(-1)))
|
|
403
|
+
return true;
|
|
404
|
+
const word = trimmed.match(/[A-Za-z_$][A-Za-z0-9_$]*$/)?.[0];
|
|
405
|
+
return word !== void 0 && REGEX_ALLOWED_AFTER_WORD.has(word);
|
|
406
|
+
}
|
|
407
|
+
function regexEnd(line, start) {
|
|
408
|
+
let i = start + 1;
|
|
409
|
+
let inClass = false;
|
|
410
|
+
let body = 0;
|
|
411
|
+
for (; i < line.length; i++) {
|
|
412
|
+
const ch = line[i];
|
|
413
|
+
if (ch === "\\") {
|
|
414
|
+
i++;
|
|
415
|
+
body++;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (inClass) {
|
|
419
|
+
if (ch === "]")
|
|
420
|
+
inClass = false;
|
|
421
|
+
} else if (ch === "[") {
|
|
422
|
+
inClass = true;
|
|
423
|
+
} else if (ch === "/") {
|
|
424
|
+
if (body === 0)
|
|
425
|
+
return -1;
|
|
426
|
+
let end = i + 1;
|
|
427
|
+
while (end < line.length && /[a-z]/i.test(line[end]))
|
|
428
|
+
end++;
|
|
429
|
+
return end;
|
|
430
|
+
}
|
|
431
|
+
body++;
|
|
432
|
+
}
|
|
433
|
+
return -1;
|
|
434
|
+
}
|
|
435
|
+
function stringEnd(line, start) {
|
|
436
|
+
const quote = line[start];
|
|
437
|
+
let i = start + 1;
|
|
438
|
+
for (; i < line.length; i++) {
|
|
439
|
+
if (line[i] === "\\") {
|
|
440
|
+
i++;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (line[i] === quote)
|
|
444
|
+
return i + 1;
|
|
445
|
+
}
|
|
446
|
+
return line.length;
|
|
447
|
+
}
|
|
448
|
+
|
|
364
449
|
// node_modules/@flagrix/scanner-core/dist/github/api-error.js
|
|
365
450
|
async function githubApiError(response, fallback = "GitHub API error") {
|
|
366
451
|
let apiMessage = "";
|
|
@@ -441,9 +526,13 @@ function applyYaraRules(content, rules, filePath) {
|
|
|
441
526
|
const isTest = isTestFile(filePath);
|
|
442
527
|
for (const rule of rules) {
|
|
443
528
|
try {
|
|
529
|
+
if (rule.fileExtensions && rule.fileExtensions.length > 0 && !rule.fileExtensions.some((ext) => filePath.endsWith(ext))) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
444
532
|
const regex = new RegExp(rule.pattern, "gi");
|
|
445
533
|
const matches2 = content.match(regex);
|
|
446
|
-
|
|
534
|
+
const required = rule.minMatches && rule.minMatches > 1 ? rule.minMatches : 1;
|
|
535
|
+
if (matches2 && matches2.length >= required) {
|
|
447
536
|
if (isTest && rule.severity !== "critical") {
|
|
448
537
|
continue;
|
|
449
538
|
}
|
|
@@ -535,6 +624,7 @@ async function scanGitHubRepo(repo, options) {
|
|
|
535
624
|
}
|
|
536
625
|
const maliciousPackages = options.signatures.maliciousPackages;
|
|
537
626
|
const yaraRules = options.signatures.yaraRules;
|
|
627
|
+
const loadedRuleIds = new Set(yaraRules.map((r) => r.id));
|
|
538
628
|
const fileContents = [];
|
|
539
629
|
for (const file of filesToScan) {
|
|
540
630
|
filesScanned++;
|
|
@@ -544,8 +634,9 @@ async function scanGitHubRepo(repo, options) {
|
|
|
544
634
|
continue;
|
|
545
635
|
const fileData = await contentResponse.json();
|
|
546
636
|
const content = atob(fileData.content || "");
|
|
637
|
+
const matchable = /\.(?:js|ts)$/.test(file.path) ? maskRegexLiterals(content) : content;
|
|
547
638
|
if (SCANNABLE_EXTENSIONS.some((ext) => file.path.endsWith(ext))) {
|
|
548
|
-
fileContents.push({ path: file.path, content });
|
|
639
|
+
fileContents.push({ path: file.path, content: matchable });
|
|
549
640
|
}
|
|
550
641
|
if (file.path.endsWith("package.json")) {
|
|
551
642
|
const pkgFindings = await scanPackageJson(content, maliciousPackages, file.path);
|
|
@@ -562,10 +653,10 @@ async function scanGitHubRepo(repo, options) {
|
|
|
562
653
|
patternsMatched += pyFindings.length;
|
|
563
654
|
}
|
|
564
655
|
if (SCANNABLE_EXTENSIONS.some((ext) => file.path.endsWith(ext))) {
|
|
565
|
-
const yaraFindings = applyYaraRules(
|
|
656
|
+
const yaraFindings = applyYaraRules(matchable, yaraRules, file.path);
|
|
566
657
|
findings.push(...yaraFindings);
|
|
567
658
|
patternsMatched += yaraFindings.length;
|
|
568
|
-
const obfuscationFindings = detectObfuscation(
|
|
659
|
+
const obfuscationFindings = detectObfuscation(matchable, file.path, loadedRuleIds);
|
|
569
660
|
findings.push(...obfuscationFindings);
|
|
570
661
|
patternsMatched += obfuscationFindings.length;
|
|
571
662
|
}
|
|
@@ -575,22 +666,22 @@ async function scanGitHubRepo(repo, options) {
|
|
|
575
666
|
findings.push(...languageFindings);
|
|
576
667
|
patternsMatched += languageFindings.length;
|
|
577
668
|
for (const file of fileContents) {
|
|
578
|
-
const secretFindings = detectHardcodedSecrets(file.content, file.path);
|
|
669
|
+
const secretFindings = detectHardcodedSecrets(file.content, file.path, loadedRuleIds);
|
|
579
670
|
findings.push(...secretFindings);
|
|
580
671
|
patternsMatched += secretFindings.length;
|
|
581
|
-
const networkFindings = detectNetworkPatterns(file.content, file.path);
|
|
672
|
+
const networkFindings = detectNetworkPatterns(file.content, file.path, loadedRuleIds);
|
|
582
673
|
findings.push(...networkFindings);
|
|
583
674
|
patternsMatched += networkFindings.length;
|
|
584
|
-
const miningFindings = detectCryptoMining(file.content, file.path);
|
|
675
|
+
const miningFindings = detectCryptoMining(file.content, file.path, loadedRuleIds);
|
|
585
676
|
findings.push(...miningFindings);
|
|
586
677
|
patternsMatched += miningFindings.length;
|
|
587
|
-
const exfiltrationFindings = detectDataExfiltration(file.content, file.path);
|
|
678
|
+
const exfiltrationFindings = detectDataExfiltration(file.content, file.path, loadedRuleIds);
|
|
588
679
|
findings.push(...exfiltrationFindings);
|
|
589
680
|
patternsMatched += exfiltrationFindings.length;
|
|
590
|
-
const backdoorFindings = detectBackdoors(file.content, file.path);
|
|
681
|
+
const backdoorFindings = detectBackdoors(file.content, file.path, loadedRuleIds);
|
|
591
682
|
findings.push(...backdoorFindings);
|
|
592
683
|
patternsMatched += backdoorFindings.length;
|
|
593
|
-
const fileAccessFindings = detectSuspiciousFileAccess(file.content, file.path);
|
|
684
|
+
const fileAccessFindings = detectSuspiciousFileAccess(file.content, file.path, loadedRuleIds);
|
|
594
685
|
findings.push(...fileAccessFindings);
|
|
595
686
|
patternsMatched += fileAccessFindings.length;
|
|
596
687
|
const socialEngFindings = detectSocialEngineering(file.content, file.path);
|
|
@@ -607,7 +698,8 @@ async function scanGitHubRepo(repo, options) {
|
|
|
607
698
|
patternsMatched += supplyChainFindings.length;
|
|
608
699
|
}
|
|
609
700
|
}
|
|
610
|
-
const
|
|
701
|
+
const allPaths = tree.tree.filter((i) => i.type === "blob").map((i) => i.path);
|
|
702
|
+
const integrityFindings = await detectCodeIntegrityIssues(fileContents, allPaths, repo);
|
|
611
703
|
findings.push(...integrityFindings);
|
|
612
704
|
patternsMatched += integrityFindings.length;
|
|
613
705
|
const riskScore = calculateRiskScore(findings);
|
|
@@ -721,11 +813,19 @@ function scanPythonDeps(content, maliciousPackages, filePath) {
|
|
|
721
813
|
}
|
|
722
814
|
return findings;
|
|
723
815
|
}
|
|
724
|
-
|
|
816
|
+
var BUILTIN_OBFUSCATION_RULE_IDS = {
|
|
817
|
+
base64: "OBF_BASE64_HEAVY",
|
|
818
|
+
hex: "OBF_HEX_STRINGS",
|
|
819
|
+
eval: "OBF_EVAL",
|
|
820
|
+
newFunction: "OBF_NEW_FUNCTION",
|
|
821
|
+
timerString: "OBF_SETTIMEOUT_STRING",
|
|
822
|
+
longLine: "OBF_LONG_LINE"
|
|
823
|
+
};
|
|
824
|
+
function detectObfuscation(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
|
|
725
825
|
const findings = [];
|
|
726
826
|
if (isTestFile(filePath))
|
|
727
827
|
return findings;
|
|
728
|
-
const base64Matches = content.match(/[A-Za-z0-9+/]{50,}={0,2}/g);
|
|
828
|
+
const base64Matches = coveredRuleIds.has(BUILTIN_OBFUSCATION_RULE_IDS.base64) ? null : content.match(/[A-Za-z0-9+/]{50,}={0,2}/g);
|
|
729
829
|
if (base64Matches && base64Matches.length > 5) {
|
|
730
830
|
const snippet = base64Matches.slice(0, 3).join("\n") + (base64Matches.length > 3 ? "\n... and more" : "");
|
|
731
831
|
findings.push({
|
|
@@ -737,7 +837,7 @@ function detectObfuscation(content, filePath) {
|
|
|
737
837
|
codeExplanation: "\u{1F513} Multiple Base64-encoded strings found. Base64 is commonly used to hide malicious payloads, URLs, or commands from code reviewers."
|
|
738
838
|
});
|
|
739
839
|
}
|
|
740
|
-
const hexMatches = content.match(/\\x[0-9a-fA-F]{2}/g);
|
|
840
|
+
const hexMatches = coveredRuleIds.has(BUILTIN_OBFUSCATION_RULE_IDS.hex) ? null : content.match(/\\x[0-9a-fA-F]{2}/g);
|
|
741
841
|
if (hexMatches && hexMatches.length > 20) {
|
|
742
842
|
findings.push({
|
|
743
843
|
severity: "medium",
|
|
@@ -749,11 +849,11 @@ function detectObfuscation(content, filePath) {
|
|
|
749
849
|
});
|
|
750
850
|
}
|
|
751
851
|
const evalPatterns = [
|
|
752
|
-
{ pattern: /eval\s*\([^)]{0,200}\)/gi, name: "eval" },
|
|
753
|
-
{ pattern: /new\s+Function\s*\([^)]{0,200}\)/gi, name: "Function constructor" },
|
|
754
|
-
{ pattern: /setTimeout\s*\(\s*["'`][^"'`]{0,200}["'`]/gi, name: "setTimeout with string" },
|
|
755
|
-
{ pattern: /setInterval\s*\(\s*["'`][^"'`]{0,200}["'`]/gi, name: "setInterval with string" }
|
|
756
|
-
];
|
|
852
|
+
{ pattern: /eval\s*\([^)]{0,200}\)/gi, name: "eval", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.eval },
|
|
853
|
+
{ pattern: /new\s+Function\s*\([^)]{0,200}\)/gi, name: "Function constructor", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.newFunction },
|
|
854
|
+
{ pattern: /setTimeout\s*\(\s*["'`][^"'`]{0,200}["'`]/gi, name: "setTimeout with string", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.timerString },
|
|
855
|
+
{ pattern: /setInterval\s*\(\s*["'`][^"'`]{0,200}["'`]/gi, name: "setInterval with string", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.timerString }
|
|
856
|
+
].filter(({ ruleId }) => !coveredRuleIds.has(ruleId));
|
|
757
857
|
for (const { pattern, name } of evalPatterns) {
|
|
758
858
|
const matches2 = content.match(pattern);
|
|
759
859
|
if (matches2 && matches2.length > 0) {
|
|
@@ -783,7 +883,7 @@ function detectObfuscation(content, filePath) {
|
|
|
783
883
|
/atob\s*\(/i,
|
|
784
884
|
/fromCharCode/i
|
|
785
885
|
];
|
|
786
|
-
const lines = content.split("\n");
|
|
886
|
+
const lines = coveredRuleIds.has(BUILTIN_OBFUSCATION_RULE_IDS.longLine) ? [] : content.split("\n");
|
|
787
887
|
const suspiciousLines = [];
|
|
788
888
|
lines.forEach((line, index) => {
|
|
789
889
|
if (line.length > 5e3) {
|
|
@@ -1029,23 +1129,23 @@ function detectSuspiciousPackageNames(content, filePath) {
|
|
|
1029
1129
|
}
|
|
1030
1130
|
return findings;
|
|
1031
1131
|
}
|
|
1032
|
-
function detectHardcodedSecrets(content, filePath) {
|
|
1132
|
+
function detectHardcodedSecrets(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
|
|
1033
1133
|
const findings = [];
|
|
1034
1134
|
if (isTestFile(filePath))
|
|
1035
1135
|
return findings;
|
|
1036
1136
|
const secretPatterns = [
|
|
1037
|
-
{ pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "API Key", severity: "critical" },
|
|
1038
|
-
{ pattern: /(?:secret[_-]?key|secret)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Secret Key", severity: "critical" },
|
|
1039
|
-
{ pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"]([^'"]{8,})['"]/gi, type: "Password", severity: "critical" },
|
|
1040
|
-
{ pattern: /(?:token|auth[_-]?token)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Auth Token", severity: "critical" },
|
|
1041
|
-
{ pattern: /(?:private[_-]?key|privatekey)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Private Key", severity: "critical" },
|
|
1042
|
-
{ pattern: /AKIA[0-9A-Z]{16}/g, type: "AWS Access Key", severity: "critical" },
|
|
1043
|
-
{ pattern: /ghp_[a-zA-Z0-9]{36}/g, type: "GitHub Personal Access Token", severity: "critical" },
|
|
1044
|
-
{ pattern: /gho_[a-zA-Z0-9]{36}/g, type: "GitHub OAuth Token", severity: "critical" },
|
|
1045
|
-
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/g, type: "Stripe Live Key", severity: "critical" },
|
|
1046
|
-
{ pattern: /(?:mongodb|mongo)[+:\/\/]{0,6}[^@\s]+:[^@\s]+@/gi, type: "MongoDB Connection String", severity: "high" },
|
|
1047
|
-
{ pattern: /postgres:\/\/[^@\s]+:[^@\s]+@/gi, type: "PostgreSQL Connection String", severity: "high" }
|
|
1048
|
-
];
|
|
1137
|
+
{ pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "API Key", severity: "critical", ruleId: "HARDCODED_API_KEY" },
|
|
1138
|
+
{ pattern: /(?:secret[_-]?key|secret)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Secret Key", severity: "critical", ruleId: "HARDCODED_API_KEY" },
|
|
1139
|
+
{ pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"]([^'"]{8,})['"]/gi, type: "Password", severity: "critical", ruleId: null },
|
|
1140
|
+
{ pattern: /(?:token|auth[_-]?token)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Auth Token", severity: "critical", ruleId: "HARDCODED_API_KEY" },
|
|
1141
|
+
{ pattern: /(?:private[_-]?key|privatekey)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Private Key", severity: "critical", ruleId: "HARDCODED_API_KEY" },
|
|
1142
|
+
{ pattern: /AKIA[0-9A-Z]{16}/g, type: "AWS Access Key", severity: "critical", ruleId: "HARDCODED_AWS_KEY" },
|
|
1143
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/g, type: "GitHub Personal Access Token", severity: "critical", ruleId: "HARDCODED_GITHUB_TOKEN" },
|
|
1144
|
+
{ pattern: /gho_[a-zA-Z0-9]{36}/g, type: "GitHub OAuth Token", severity: "critical", ruleId: "HARDCODED_GITHUB_TOKEN" },
|
|
1145
|
+
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/g, type: "Stripe Live Key", severity: "critical", ruleId: "HARDCODED_STRIPE_KEY" },
|
|
1146
|
+
{ pattern: /(?:mongodb|mongo)[+:\/\/]{0,6}[^@\s]+:[^@\s]+@/gi, type: "MongoDB Connection String", severity: "high", ruleId: "HARDCODED_DB_CONNECTION" },
|
|
1147
|
+
{ pattern: /postgres:\/\/[^@\s]+:[^@\s]+@/gi, type: "PostgreSQL Connection String", severity: "high", ruleId: "HARDCODED_DB_CONNECTION" }
|
|
1148
|
+
].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
|
|
1049
1149
|
const foundSecrets = [];
|
|
1050
1150
|
const matchedRegexes = [];
|
|
1051
1151
|
for (const { pattern, type, severity } of secretPatterns) {
|
|
@@ -1069,7 +1169,7 @@ function detectHardcodedSecrets(content, filePath) {
|
|
|
1069
1169
|
}
|
|
1070
1170
|
return findings;
|
|
1071
1171
|
}
|
|
1072
|
-
function detectNetworkPatterns(content, filePath) {
|
|
1172
|
+
function detectNetworkPatterns(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
|
|
1073
1173
|
const findings = [];
|
|
1074
1174
|
if (isTestFile(filePath))
|
|
1075
1175
|
return findings;
|
|
@@ -1078,13 +1178,13 @@ function detectNetworkPatterns(content, filePath) {
|
|
|
1078
1178
|
// dotted/number sequence (avoids flagging version strings like "1.2.3.4.5" or
|
|
1079
1179
|
// impossible IPs like "999.999.999.999"), and excluding private/link-local ranges.
|
|
1080
1180
|
{ pattern: /(?:https?:\/\/)?(?<![\d.])(?!(?:127\.|10\.|192\.168\.|169\.254\.|172\.(?:1[6-9]|2\d|3[01])\.))(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}(?![\d.])(?::\d{1,5})?/g, type: "Hardcoded IP Address", severity: "high" },
|
|
1081
|
-
{ pattern: /https?:\/\/discord(?:app)?\.com\/api\/webhooks\/\d+\/[a-zA-Z0-9_-]+/gi, type: "Discord Webhook", severity: "critical" },
|
|
1082
|
-
{ pattern: /\d{8,10}:[a-zA-Z0-9_-]{35}/g, type: "Telegram Bot Token", severity: "high" },
|
|
1083
|
-
{ pattern: /(?:pastebin\.com|hastebin\.com|paste\.ee)\/[a-zA-Z0-9]+/gi, type: "Pastebin URL", severity: "medium" },
|
|
1084
|
-
{ pattern: /(?:bit\.ly|tinyurl\.com|t\.co)\/[a-zA-Z0-9]+/gi, type: "URL Shortener", severity: "medium" },
|
|
1085
|
-
{ pattern: /https?:\/\/[a-zA-Z0-9-]+\.(?:tk|ml|ga|cf|gq|onion|xyz)(?:\/|$)/gi, type: "Suspicious Domain TLD", severity: "high" },
|
|
1086
|
-
{ pattern: /https?:\/\/[a-zA-Z0-9-]+\.ngrok\.io/gi, type: "Ngrok Tunnel", severity: "medium" }
|
|
1087
|
-
];
|
|
1181
|
+
{ pattern: /https?:\/\/discord(?:app)?\.com\/api\/webhooks\/\d+\/[a-zA-Z0-9_-]+/gi, type: "Discord Webhook", severity: "critical", ruleId: "NETWORK_DISCORD_WEBHOOK" },
|
|
1182
|
+
{ pattern: /\d{8,10}:[a-zA-Z0-9_-]{35}/g, type: "Telegram Bot Token", severity: "high", ruleId: "NETWORK_TELEGRAM_TOKEN" },
|
|
1183
|
+
{ pattern: /(?:pastebin\.com|hastebin\.com|paste\.ee)\/[a-zA-Z0-9]+/gi, type: "Pastebin URL", severity: "medium", ruleId: "NETWORK_PASTEBIN" },
|
|
1184
|
+
{ pattern: /(?:bit\.ly|tinyurl\.com|t\.co)\/[a-zA-Z0-9]+/gi, type: "URL Shortener", severity: "medium", ruleId: "NETWORK_URL_SHORTENER" },
|
|
1185
|
+
{ pattern: /https?:\/\/[a-zA-Z0-9-]+\.(?:tk|ml|ga|cf|gq|onion|xyz)(?:\/|$)/gi, type: "Suspicious Domain TLD", severity: "high", ruleId: "NETWORK_SUSPICIOUS_TLD" },
|
|
1186
|
+
{ pattern: /https?:\/\/[a-zA-Z0-9-]+\.ngrok\.io/gi, type: "Ngrok Tunnel", severity: "medium", ruleId: "NETWORK_NGROK_TUNNEL" }
|
|
1187
|
+
].filter((p) => !("ruleId" in p) || !coveredRuleIds.has(p.ruleId));
|
|
1088
1188
|
for (const { pattern, type, severity } of suspiciousPatterns) {
|
|
1089
1189
|
const matches2 = content.match(pattern);
|
|
1090
1190
|
if (matches2 && matches2.length > 0) {
|
|
@@ -1108,8 +1208,10 @@ function detectNetworkPatterns(content, filePath) {
|
|
|
1108
1208
|
}
|
|
1109
1209
|
return findings;
|
|
1110
1210
|
}
|
|
1111
|
-
function detectCryptoMining(content, filePath) {
|
|
1211
|
+
function detectCryptoMining(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
|
|
1112
1212
|
const findings = [];
|
|
1213
|
+
if (coveredRuleIds.has("CRYPTO_MINER"))
|
|
1214
|
+
return findings;
|
|
1113
1215
|
const miningPatterns = [
|
|
1114
1216
|
/coinhive|coin-hive|crypto-loot|cryptoloot|jsecoin/gi,
|
|
1115
1217
|
/stratum\+tcp|stratum\.|\bpool\./gi,
|
|
@@ -1142,18 +1244,18 @@ function detectCryptoMining(content, filePath) {
|
|
|
1142
1244
|
}
|
|
1143
1245
|
return findings;
|
|
1144
1246
|
}
|
|
1145
|
-
function detectDataExfiltration(content, filePath) {
|
|
1247
|
+
function detectDataExfiltration(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
|
|
1146
1248
|
const findings = [];
|
|
1147
1249
|
if (isTestFile(filePath))
|
|
1148
1250
|
return findings;
|
|
1149
1251
|
const exfiltrationPatterns = [
|
|
1150
|
-
{ pattern: /navigator\.clipboard\.read(?:Text)?\s*\(/gi, type: "Clipboard Access", severity: "high" },
|
|
1151
|
-
{ pattern: /localStorage\.getItem|sessionStorage\.getItem/gi, type: "Storage Access", severity: "medium" },
|
|
1152
|
-
{ pattern: /document\.cookie/gi, type: "Cookie Access", severity: "high" },
|
|
1153
|
-
{ pattern: /(?:addEventListener|on)\s*\(\s*['"](?:keydown|keypress|keyup)['"]/gi, type: "Keylogger Pattern", severity: "critical" },
|
|
1154
|
-
{ pattern: /(?:input|password|email)[\w-]*\.value/gi, type: "Form Data Access", severity: "medium" },
|
|
1155
|
-
{ pattern: /new\s+FormData\s*\([^)]*\)[\s\S]{0,100}(?:fetch|axios|XMLHttpRequest)/gi, type: "Form Data Transmission", severity: "high" }
|
|
1156
|
-
];
|
|
1252
|
+
{ pattern: /navigator\.clipboard\.read(?:Text)?\s*\(/gi, type: "Clipboard Access", severity: "high", ruleId: "EXFIL_CLIPBOARD" },
|
|
1253
|
+
{ pattern: /localStorage\.getItem|sessionStorage\.getItem/gi, type: "Storage Access", severity: "medium", ruleId: null },
|
|
1254
|
+
{ pattern: /document\.cookie/gi, type: "Cookie Access", severity: "high", ruleId: "EXFIL_COOKIE" },
|
|
1255
|
+
{ pattern: /(?:addEventListener|on)\s*\(\s*['"](?:keydown|keypress|keyup)['"]/gi, type: "Keylogger Pattern", severity: "critical", ruleId: "EXFIL_KEYLOGGER" },
|
|
1256
|
+
{ pattern: /(?:input|password|email)[\w-]*\.value/gi, type: "Form Data Access", severity: "medium", ruleId: null },
|
|
1257
|
+
{ pattern: /new\s+FormData\s*\([^)]*\)[\s\S]{0,100}(?:fetch|axios|XMLHttpRequest)/gi, type: "Form Data Transmission", severity: "high", ruleId: null }
|
|
1258
|
+
].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
|
|
1157
1259
|
const detectedPatterns = [];
|
|
1158
1260
|
const matchedRegexes = [];
|
|
1159
1261
|
let highestSeverity = "medium";
|
|
@@ -1178,16 +1280,16 @@ function detectDataExfiltration(content, filePath) {
|
|
|
1178
1280
|
}
|
|
1179
1281
|
return findings;
|
|
1180
1282
|
}
|
|
1181
|
-
function detectBackdoors(content, filePath) {
|
|
1283
|
+
function detectBackdoors(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
|
|
1182
1284
|
const findings = [];
|
|
1183
1285
|
const backdoorPatterns = [
|
|
1184
|
-
{ pattern: /eval\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Remote Code Execution Endpoint", severity: "critical" },
|
|
1185
|
-
{ pattern: /exec\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Command Injection Endpoint", severity: "critical" },
|
|
1186
|
-
{ pattern: /require\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Dynamic Require", severity: "critical" },
|
|
1187
|
-
{ pattern: /new\s+Function\s*\([^)]*(?:request|req)/gi, type: "Dynamic Function from Request", severity: "critical" },
|
|
1188
|
-
{ pattern: /(?:admin|debug|backdoor|shell)[\w]*\s*[:=]\s*(?:true|1|"[^"]*")\s*(?:\/\/|#)?\s*(?:TODO|FIXME|HACK)?/gi, type: "Debug/Admin Flag", severity: "high" },
|
|
1189
|
-
{ pattern: /(?:password|auth)\s*(?:===?|==)\s*['"][^'"]{0,20}['"]\s*\)/gi, type: "Hardcoded Auth Bypass", severity: "critical" }
|
|
1190
|
-
];
|
|
1286
|
+
{ pattern: /eval\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Remote Code Execution Endpoint", severity: "critical", ruleId: "BACKDOOR_RCE_ENDPOINT" },
|
|
1287
|
+
{ pattern: /exec\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Command Injection Endpoint", severity: "critical", ruleId: "BACKDOOR_RCE_ENDPOINT" },
|
|
1288
|
+
{ pattern: /require\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Dynamic Require", severity: "critical", ruleId: "BACKDOOR_DYNAMIC_REQUIRE" },
|
|
1289
|
+
{ pattern: /new\s+Function\s*\([^)]*(?:request|req)/gi, type: "Dynamic Function from Request", severity: "critical", ruleId: null },
|
|
1290
|
+
{ pattern: /(?:admin|debug|backdoor|shell)[\w]*\s*[:=]\s*(?:true|1|"[^"]*")\s*(?:\/\/|#)?\s*(?:TODO|FIXME|HACK)?/gi, type: "Debug/Admin Flag", severity: "high", ruleId: null },
|
|
1291
|
+
{ pattern: /(?:password|auth)\s*(?:===?|==)\s*['"][^'"]{0,20}['"]\s*\)/gi, type: "Hardcoded Auth Bypass", severity: "critical", ruleId: "BACKDOOR_HARDCODED_AUTH" }
|
|
1292
|
+
].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
|
|
1191
1293
|
for (const { pattern, type, severity } of backdoorPatterns) {
|
|
1192
1294
|
const matches2 = content.match(pattern);
|
|
1193
1295
|
if (matches2) {
|
|
@@ -1242,17 +1344,17 @@ function detectSupplyChainRisks(content, filePath) {
|
|
|
1242
1344
|
}
|
|
1243
1345
|
return findings;
|
|
1244
1346
|
}
|
|
1245
|
-
function detectSuspiciousFileAccess(content, filePath) {
|
|
1347
|
+
function detectSuspiciousFileAccess(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
|
|
1246
1348
|
const findings = [];
|
|
1247
1349
|
if (isTestFile(filePath))
|
|
1248
1350
|
return findings;
|
|
1249
1351
|
const fileAccessPatterns = [
|
|
1250
|
-
{ pattern: /(?:fs\.read|readFileSync|readFile)\s*\([^)]*(?:\.ssh|\.aws|\.gnupg|\.docker|id_rsa|credentials)/gi, type: "SSH/AWS Credentials Access", severity: "critical" },
|
|
1251
|
-
{ pattern: /(?:fs\.read|readFileSync|readFile)\s*\([^)]*(?:etc\/passwd|etc\/shadow|\.bash_history|\.zsh_history)/gi, type: "System File Access", severity: "critical" },
|
|
1252
|
-
{ pattern: /(?:fs\.read|readFileSync|readFile)\s*\([^)]*(?:Chrome|Firefox|Safari|Edge)[\w\s/\\]*(?:Cookies|Login|History)/gi, type: "Browser Data Access", severity: "critical" },
|
|
1253
|
-
{ pattern: /(?:fs\.write|writeFileSync|writeFile)\s*\([^)]*(?:\/etc|\/sys|\/bin|C:\\\\Windows)/gi, type: "System Directory Write", severity: "critical" },
|
|
1254
|
-
{ pattern: /(?:fs\.unlink|unlinkSync|rmSync|rm\s+-rf)/gi, type: "File Deletion", severity: "medium" }
|
|
1255
|
-
];
|
|
1352
|
+
{ pattern: /(?:fs\.read|readFileSync|readFile)\s*\([^)]*(?:\.ssh|\.aws|\.gnupg|\.docker|id_rsa|credentials)/gi, type: "SSH/AWS Credentials Access", severity: "critical", ruleId: "FILE_ACCESS_CREDENTIALS" },
|
|
1353
|
+
{ pattern: /(?:fs\.read|readFileSync|readFile)\s*\([^)]*(?:etc\/passwd|etc\/shadow|\.bash_history|\.zsh_history)/gi, type: "System File Access", severity: "critical", ruleId: "FILE_ACCESS_SYSTEM" },
|
|
1354
|
+
{ pattern: /(?:fs\.read|readFileSync|readFile)\s*\([^)]*(?:Chrome|Firefox|Safari|Edge)[\w\s/\\]*(?:Cookies|Login|History)/gi, type: "Browser Data Access", severity: "critical", ruleId: "FILE_ACCESS_BROWSER_DATA" },
|
|
1355
|
+
{ pattern: /(?:fs\.write|writeFileSync|writeFile)\s*\([^)]*(?:\/etc|\/sys|\/bin|C:\\\\Windows)/gi, type: "System Directory Write", severity: "critical", ruleId: null },
|
|
1356
|
+
{ pattern: /(?:fs\.unlink|unlinkSync|rmSync|rm\s+-rf)/gi, type: "File Deletion", severity: "medium", ruleId: null }
|
|
1357
|
+
].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
|
|
1256
1358
|
for (const { pattern, type, severity } of fileAccessPatterns) {
|
|
1257
1359
|
const matches2 = content.match(pattern);
|
|
1258
1360
|
if (matches2) {
|
|
@@ -1269,7 +1371,7 @@ function detectSuspiciousFileAccess(content, filePath) {
|
|
|
1269
1371
|
}
|
|
1270
1372
|
return findings;
|
|
1271
1373
|
}
|
|
1272
|
-
async function detectCodeIntegrityIssues(files, _repo) {
|
|
1374
|
+
async function detectCodeIntegrityIssues(files, allPaths, _repo) {
|
|
1273
1375
|
const findings = [];
|
|
1274
1376
|
for (const file of files) {
|
|
1275
1377
|
if (file.path.endsWith(".min.js") || file.path.endsWith(".min.css"))
|
|
@@ -1291,8 +1393,9 @@ async function detectCodeIntegrityIssues(files, _repo) {
|
|
|
1291
1393
|
}
|
|
1292
1394
|
}
|
|
1293
1395
|
}
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1396
|
+
const basename = (p) => p.split("/").pop() ?? p;
|
|
1397
|
+
const hasLicense = allPaths.some((p) => /^(LICENSE|COPYING)/i.test(basename(p)));
|
|
1398
|
+
if (!hasLicense && allPaths.length > 5) {
|
|
1296
1399
|
findings.push({
|
|
1297
1400
|
severity: "low",
|
|
1298
1401
|
type: "CODE_INTEGRITY_ISSUE",
|
|
@@ -1300,8 +1403,8 @@ async function detectCodeIntegrityIssues(files, _repo) {
|
|
|
1300
1403
|
codeExplanation: "\u{1F4C4} Missing license file. Legitimate open-source projects typically include a license. Absence may indicate a quickly assembled malicious repository."
|
|
1301
1404
|
});
|
|
1302
1405
|
}
|
|
1303
|
-
const hasReadme =
|
|
1304
|
-
if (!hasReadme &&
|
|
1406
|
+
const hasReadme = allPaths.some((p) => /^README/i.test(basename(p)));
|
|
1407
|
+
if (!hasReadme && allPaths.length > 5) {
|
|
1305
1408
|
findings.push({
|
|
1306
1409
|
severity: "low",
|
|
1307
1410
|
type: "CODE_INTEGRITY_ISSUE",
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
scanGitHubRepo,
|
|
12
12
|
scanGitHubUser,
|
|
13
13
|
wantJson
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-AS7DXMSG.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
17
|
import { parseArgs } from "util";
|
|
@@ -132,7 +132,7 @@ async function main() {
|
|
|
132
132
|
}
|
|
133
133
|
return runScanUser(target, { json: values.json, token: values.token });
|
|
134
134
|
case "mcp": {
|
|
135
|
-
const { runMcp } = await import("./mcp-
|
|
135
|
+
const { runMcp } = await import("./mcp-LS7GRB2C.js");
|
|
136
136
|
return runMcp();
|
|
137
137
|
}
|
|
138
138
|
default:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagrix",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Scan GitHub repos and profiles for malware before you clone — CLI and MCP server for the Flagrix scanner",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"zod": "^3.24.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@flagrix/scanner-core": "
|
|
54
|
+
"@flagrix/scanner-core": "^0.1.2",
|
|
55
55
|
"@types/node": "^20.0.0",
|
|
56
56
|
"tsup": "^8.0.0",
|
|
57
57
|
"typescript": "^5.0.0",
|