flagrix 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "2025.01.26.001",
3
- "lastUpdated": "2026-07-04T18:31:51.928Z",
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": "high"
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
- "id": "OBFUSCATED_CODE_EVAL_DYNAMIC",
235
- "name": "Dynamic Code Execution",
236
- "pattern": "eval\\s*\\([^)]{0,200}\\)|new\\s+Function\\s*\\([^)]{0,200}\\)|setTimeout\\s*\\(\\s*[\"'`][^\"'`]{0,200}[\"'`]|setInterval\\s*\\(\\s*[\"'`][^\"'`]{0,200}[\"'`]",
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": [],
@@ -441,9 +441,13 @@ function applyYaraRules(content, rules, filePath) {
441
441
  const isTest = isTestFile(filePath);
442
442
  for (const rule of rules) {
443
443
  try {
444
+ if (rule.fileExtensions && rule.fileExtensions.length > 0 && !rule.fileExtensions.some((ext) => filePath.endsWith(ext))) {
445
+ continue;
446
+ }
444
447
  const regex = new RegExp(rule.pattern, "gi");
445
448
  const matches2 = content.match(regex);
446
- if (matches2 && matches2.length > 0) {
449
+ const required = rule.minMatches && rule.minMatches > 1 ? rule.minMatches : 1;
450
+ if (matches2 && matches2.length >= required) {
447
451
  if (isTest && rule.severity !== "critical") {
448
452
  continue;
449
453
  }
@@ -535,6 +539,7 @@ async function scanGitHubRepo(repo, options) {
535
539
  }
536
540
  const maliciousPackages = options.signatures.maliciousPackages;
537
541
  const yaraRules = options.signatures.yaraRules;
542
+ const loadedRuleIds = new Set(yaraRules.map((r) => r.id));
538
543
  const fileContents = [];
539
544
  for (const file of filesToScan) {
540
545
  filesScanned++;
@@ -565,7 +570,7 @@ async function scanGitHubRepo(repo, options) {
565
570
  const yaraFindings = applyYaraRules(content, yaraRules, file.path);
566
571
  findings.push(...yaraFindings);
567
572
  patternsMatched += yaraFindings.length;
568
- const obfuscationFindings = detectObfuscation(content, file.path);
573
+ const obfuscationFindings = detectObfuscation(content, file.path, loadedRuleIds);
569
574
  findings.push(...obfuscationFindings);
570
575
  patternsMatched += obfuscationFindings.length;
571
576
  }
@@ -575,22 +580,22 @@ async function scanGitHubRepo(repo, options) {
575
580
  findings.push(...languageFindings);
576
581
  patternsMatched += languageFindings.length;
577
582
  for (const file of fileContents) {
578
- const secretFindings = detectHardcodedSecrets(file.content, file.path);
583
+ const secretFindings = detectHardcodedSecrets(file.content, file.path, loadedRuleIds);
579
584
  findings.push(...secretFindings);
580
585
  patternsMatched += secretFindings.length;
581
- const networkFindings = detectNetworkPatterns(file.content, file.path);
586
+ const networkFindings = detectNetworkPatterns(file.content, file.path, loadedRuleIds);
582
587
  findings.push(...networkFindings);
583
588
  patternsMatched += networkFindings.length;
584
- const miningFindings = detectCryptoMining(file.content, file.path);
589
+ const miningFindings = detectCryptoMining(file.content, file.path, loadedRuleIds);
585
590
  findings.push(...miningFindings);
586
591
  patternsMatched += miningFindings.length;
587
- const exfiltrationFindings = detectDataExfiltration(file.content, file.path);
592
+ const exfiltrationFindings = detectDataExfiltration(file.content, file.path, loadedRuleIds);
588
593
  findings.push(...exfiltrationFindings);
589
594
  patternsMatched += exfiltrationFindings.length;
590
- const backdoorFindings = detectBackdoors(file.content, file.path);
595
+ const backdoorFindings = detectBackdoors(file.content, file.path, loadedRuleIds);
591
596
  findings.push(...backdoorFindings);
592
597
  patternsMatched += backdoorFindings.length;
593
- const fileAccessFindings = detectSuspiciousFileAccess(file.content, file.path);
598
+ const fileAccessFindings = detectSuspiciousFileAccess(file.content, file.path, loadedRuleIds);
594
599
  findings.push(...fileAccessFindings);
595
600
  patternsMatched += fileAccessFindings.length;
596
601
  const socialEngFindings = detectSocialEngineering(file.content, file.path);
@@ -721,11 +726,19 @@ function scanPythonDeps(content, maliciousPackages, filePath) {
721
726
  }
722
727
  return findings;
723
728
  }
724
- function detectObfuscation(content, filePath) {
729
+ var BUILTIN_OBFUSCATION_RULE_IDS = {
730
+ base64: "OBF_BASE64_HEAVY",
731
+ hex: "OBF_HEX_STRINGS",
732
+ eval: "OBF_EVAL",
733
+ newFunction: "OBF_NEW_FUNCTION",
734
+ timerString: "OBF_SETTIMEOUT_STRING",
735
+ longLine: "OBF_LONG_LINE"
736
+ };
737
+ function detectObfuscation(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
725
738
  const findings = [];
726
739
  if (isTestFile(filePath))
727
740
  return findings;
728
- const base64Matches = content.match(/[A-Za-z0-9+/]{50,}={0,2}/g);
741
+ const base64Matches = coveredRuleIds.has(BUILTIN_OBFUSCATION_RULE_IDS.base64) ? null : content.match(/[A-Za-z0-9+/]{50,}={0,2}/g);
729
742
  if (base64Matches && base64Matches.length > 5) {
730
743
  const snippet = base64Matches.slice(0, 3).join("\n") + (base64Matches.length > 3 ? "\n... and more" : "");
731
744
  findings.push({
@@ -737,7 +750,7 @@ function detectObfuscation(content, filePath) {
737
750
  codeExplanation: "\u{1F513} Multiple Base64-encoded strings found. Base64 is commonly used to hide malicious payloads, URLs, or commands from code reviewers."
738
751
  });
739
752
  }
740
- const hexMatches = content.match(/\\x[0-9a-fA-F]{2}/g);
753
+ const hexMatches = coveredRuleIds.has(BUILTIN_OBFUSCATION_RULE_IDS.hex) ? null : content.match(/\\x[0-9a-fA-F]{2}/g);
741
754
  if (hexMatches && hexMatches.length > 20) {
742
755
  findings.push({
743
756
  severity: "medium",
@@ -749,11 +762,11 @@ function detectObfuscation(content, filePath) {
749
762
  });
750
763
  }
751
764
  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
- ];
765
+ { pattern: /eval\s*\([^)]{0,200}\)/gi, name: "eval", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.eval },
766
+ { pattern: /new\s+Function\s*\([^)]{0,200}\)/gi, name: "Function constructor", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.newFunction },
767
+ { pattern: /setTimeout\s*\(\s*["'`][^"'`]{0,200}["'`]/gi, name: "setTimeout with string", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.timerString },
768
+ { pattern: /setInterval\s*\(\s*["'`][^"'`]{0,200}["'`]/gi, name: "setInterval with string", ruleId: BUILTIN_OBFUSCATION_RULE_IDS.timerString }
769
+ ].filter(({ ruleId }) => !coveredRuleIds.has(ruleId));
757
770
  for (const { pattern, name } of evalPatterns) {
758
771
  const matches2 = content.match(pattern);
759
772
  if (matches2 && matches2.length > 0) {
@@ -783,7 +796,7 @@ function detectObfuscation(content, filePath) {
783
796
  /atob\s*\(/i,
784
797
  /fromCharCode/i
785
798
  ];
786
- const lines = content.split("\n");
799
+ const lines = coveredRuleIds.has(BUILTIN_OBFUSCATION_RULE_IDS.longLine) ? [] : content.split("\n");
787
800
  const suspiciousLines = [];
788
801
  lines.forEach((line, index) => {
789
802
  if (line.length > 5e3) {
@@ -1029,23 +1042,23 @@ function detectSuspiciousPackageNames(content, filePath) {
1029
1042
  }
1030
1043
  return findings;
1031
1044
  }
1032
- function detectHardcodedSecrets(content, filePath) {
1045
+ function detectHardcodedSecrets(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
1033
1046
  const findings = [];
1034
1047
  if (isTestFile(filePath))
1035
1048
  return findings;
1036
1049
  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
- ];
1050
+ { pattern: /(?:api[_-]?key|apikey)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "API Key", severity: "critical", ruleId: "HARDCODED_API_KEY" },
1051
+ { pattern: /(?:secret[_-]?key|secret)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Secret Key", severity: "critical", ruleId: "HARDCODED_API_KEY" },
1052
+ { pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"]([^'"]{8,})['"]/gi, type: "Password", severity: "critical", ruleId: null },
1053
+ { pattern: /(?:token|auth[_-]?token)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Auth Token", severity: "critical", ruleId: "HARDCODED_API_KEY" },
1054
+ { pattern: /(?:private[_-]?key|privatekey)\s*[:=]\s*['"]([^'"]{20,})['"]/gi, type: "Private Key", severity: "critical", ruleId: "HARDCODED_API_KEY" },
1055
+ { pattern: /AKIA[0-9A-Z]{16}/g, type: "AWS Access Key", severity: "critical", ruleId: "HARDCODED_AWS_KEY" },
1056
+ { pattern: /ghp_[a-zA-Z0-9]{36}/g, type: "GitHub Personal Access Token", severity: "critical", ruleId: "HARDCODED_GITHUB_TOKEN" },
1057
+ { pattern: /gho_[a-zA-Z0-9]{36}/g, type: "GitHub OAuth Token", severity: "critical", ruleId: "HARDCODED_GITHUB_TOKEN" },
1058
+ { pattern: /sk_live_[a-zA-Z0-9]{24,}/g, type: "Stripe Live Key", severity: "critical", ruleId: "HARDCODED_STRIPE_KEY" },
1059
+ { pattern: /(?:mongodb|mongo)[+:\/\/]{0,6}[^@\s]+:[^@\s]+@/gi, type: "MongoDB Connection String", severity: "high", ruleId: "HARDCODED_DB_CONNECTION" },
1060
+ { pattern: /postgres:\/\/[^@\s]+:[^@\s]+@/gi, type: "PostgreSQL Connection String", severity: "high", ruleId: "HARDCODED_DB_CONNECTION" }
1061
+ ].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
1049
1062
  const foundSecrets = [];
1050
1063
  const matchedRegexes = [];
1051
1064
  for (const { pattern, type, severity } of secretPatterns) {
@@ -1069,7 +1082,7 @@ function detectHardcodedSecrets(content, filePath) {
1069
1082
  }
1070
1083
  return findings;
1071
1084
  }
1072
- function detectNetworkPatterns(content, filePath) {
1085
+ function detectNetworkPatterns(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
1073
1086
  const findings = [];
1074
1087
  if (isTestFile(filePath))
1075
1088
  return findings;
@@ -1078,13 +1091,13 @@ function detectNetworkPatterns(content, filePath) {
1078
1091
  // dotted/number sequence (avoids flagging version strings like "1.2.3.4.5" or
1079
1092
  // impossible IPs like "999.999.999.999"), and excluding private/link-local ranges.
1080
1093
  { 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
- ];
1094
+ { pattern: /https?:\/\/discord(?:app)?\.com\/api\/webhooks\/\d+\/[a-zA-Z0-9_-]+/gi, type: "Discord Webhook", severity: "critical", ruleId: "NETWORK_DISCORD_WEBHOOK" },
1095
+ { pattern: /\d{8,10}:[a-zA-Z0-9_-]{35}/g, type: "Telegram Bot Token", severity: "high", ruleId: "NETWORK_TELEGRAM_TOKEN" },
1096
+ { pattern: /(?:pastebin\.com|hastebin\.com|paste\.ee)\/[a-zA-Z0-9]+/gi, type: "Pastebin URL", severity: "medium", ruleId: "NETWORK_PASTEBIN" },
1097
+ { pattern: /(?:bit\.ly|tinyurl\.com|t\.co)\/[a-zA-Z0-9]+/gi, type: "URL Shortener", severity: "medium", ruleId: "NETWORK_URL_SHORTENER" },
1098
+ { pattern: /https?:\/\/[a-zA-Z0-9-]+\.(?:tk|ml|ga|cf|gq|onion|xyz)(?:\/|$)/gi, type: "Suspicious Domain TLD", severity: "high", ruleId: "NETWORK_SUSPICIOUS_TLD" },
1099
+ { pattern: /https?:\/\/[a-zA-Z0-9-]+\.ngrok\.io/gi, type: "Ngrok Tunnel", severity: "medium", ruleId: "NETWORK_NGROK_TUNNEL" }
1100
+ ].filter((p) => !("ruleId" in p) || !coveredRuleIds.has(p.ruleId));
1088
1101
  for (const { pattern, type, severity } of suspiciousPatterns) {
1089
1102
  const matches2 = content.match(pattern);
1090
1103
  if (matches2 && matches2.length > 0) {
@@ -1108,8 +1121,10 @@ function detectNetworkPatterns(content, filePath) {
1108
1121
  }
1109
1122
  return findings;
1110
1123
  }
1111
- function detectCryptoMining(content, filePath) {
1124
+ function detectCryptoMining(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
1112
1125
  const findings = [];
1126
+ if (coveredRuleIds.has("CRYPTO_MINER"))
1127
+ return findings;
1113
1128
  const miningPatterns = [
1114
1129
  /coinhive|coin-hive|crypto-loot|cryptoloot|jsecoin/gi,
1115
1130
  /stratum\+tcp|stratum\.|\bpool\./gi,
@@ -1142,18 +1157,18 @@ function detectCryptoMining(content, filePath) {
1142
1157
  }
1143
1158
  return findings;
1144
1159
  }
1145
- function detectDataExfiltration(content, filePath) {
1160
+ function detectDataExfiltration(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
1146
1161
  const findings = [];
1147
1162
  if (isTestFile(filePath))
1148
1163
  return findings;
1149
1164
  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
- ];
1165
+ { pattern: /navigator\.clipboard\.read(?:Text)?\s*\(/gi, type: "Clipboard Access", severity: "high", ruleId: "EXFIL_CLIPBOARD" },
1166
+ { pattern: /localStorage\.getItem|sessionStorage\.getItem/gi, type: "Storage Access", severity: "medium", ruleId: null },
1167
+ { pattern: /document\.cookie/gi, type: "Cookie Access", severity: "high", ruleId: "EXFIL_COOKIE" },
1168
+ { pattern: /(?:addEventListener|on)\s*\(\s*['"](?:keydown|keypress|keyup)['"]/gi, type: "Keylogger Pattern", severity: "critical", ruleId: "EXFIL_KEYLOGGER" },
1169
+ { pattern: /(?:input|password|email)[\w-]*\.value/gi, type: "Form Data Access", severity: "medium", ruleId: null },
1170
+ { pattern: /new\s+FormData\s*\([^)]*\)[\s\S]{0,100}(?:fetch|axios|XMLHttpRequest)/gi, type: "Form Data Transmission", severity: "high", ruleId: null }
1171
+ ].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
1157
1172
  const detectedPatterns = [];
1158
1173
  const matchedRegexes = [];
1159
1174
  let highestSeverity = "medium";
@@ -1178,16 +1193,16 @@ function detectDataExfiltration(content, filePath) {
1178
1193
  }
1179
1194
  return findings;
1180
1195
  }
1181
- function detectBackdoors(content, filePath) {
1196
+ function detectBackdoors(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
1182
1197
  const findings = [];
1183
1198
  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
- ];
1199
+ { pattern: /eval\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Remote Code Execution Endpoint", severity: "critical", ruleId: "BACKDOOR_RCE_ENDPOINT" },
1200
+ { pattern: /exec\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Command Injection Endpoint", severity: "critical", ruleId: "BACKDOOR_RCE_ENDPOINT" },
1201
+ { pattern: /require\s*\(\s*(?:request|req)(?:\.|\.body|\.query)/gi, type: "Dynamic Require", severity: "critical", ruleId: "BACKDOOR_DYNAMIC_REQUIRE" },
1202
+ { pattern: /new\s+Function\s*\([^)]*(?:request|req)/gi, type: "Dynamic Function from Request", severity: "critical", ruleId: null },
1203
+ { pattern: /(?:admin|debug|backdoor|shell)[\w]*\s*[:=]\s*(?:true|1|"[^"]*")\s*(?:\/\/|#)?\s*(?:TODO|FIXME|HACK)?/gi, type: "Debug/Admin Flag", severity: "high", ruleId: null },
1204
+ { pattern: /(?:password|auth)\s*(?:===?|==)\s*['"][^'"]{0,20}['"]\s*\)/gi, type: "Hardcoded Auth Bypass", severity: "critical", ruleId: "BACKDOOR_HARDCODED_AUTH" }
1205
+ ].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
1191
1206
  for (const { pattern, type, severity } of backdoorPatterns) {
1192
1207
  const matches2 = content.match(pattern);
1193
1208
  if (matches2) {
@@ -1242,17 +1257,17 @@ function detectSupplyChainRisks(content, filePath) {
1242
1257
  }
1243
1258
  return findings;
1244
1259
  }
1245
- function detectSuspiciousFileAccess(content, filePath) {
1260
+ function detectSuspiciousFileAccess(content, filePath, coveredRuleIds = /* @__PURE__ */ new Set()) {
1246
1261
  const findings = [];
1247
1262
  if (isTestFile(filePath))
1248
1263
  return findings;
1249
1264
  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
- ];
1265
+ { 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" },
1266
+ { 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" },
1267
+ { 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" },
1268
+ { pattern: /(?:fs\.write|writeFileSync|writeFile)\s*\([^)]*(?:\/etc|\/sys|\/bin|C:\\\\Windows)/gi, type: "System Directory Write", severity: "critical", ruleId: null },
1269
+ { pattern: /(?:fs\.unlink|unlinkSync|rmSync|rm\s+-rf)/gi, type: "File Deletion", severity: "medium", ruleId: null }
1270
+ ].filter(({ ruleId }) => !ruleId || !coveredRuleIds.has(ruleId));
1256
1271
  for (const { pattern, type, severity } of fileAccessPatterns) {
1257
1272
  const matches2 = content.match(pattern);
1258
1273
  if (matches2) {
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  scanGitHubRepo,
12
12
  scanGitHubUser,
13
13
  wantJson
14
- } from "./chunk-FJUDM3F7.js";
14
+ } from "./chunk-QKP4NQM3.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-S44QVYNH.js");
135
+ const { runMcp } = await import("./mcp-B4RQXV5T.js");
136
136
  return runMcp();
137
137
  }
138
138
  default:
@@ -7,7 +7,7 @@ import {
7
7
  scanGitHubRepo,
8
8
  scanGitHubUser,
9
9
  securityScore
10
- } from "./chunk-FJUDM3F7.js";
10
+ } from "./chunk-QKP4NQM3.js";
11
11
 
12
12
  // src/commands/mcp.ts
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flagrix",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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": "github:flagrix-io/flagrix-scanner-core",
54
+ "@flagrix/scanner-core": "^0.1.0",
55
55
  "@types/node": "^20.0.0",
56
56
  "tsup": "^8.0.0",
57
57
  "typescript": "^5.0.0",