muaddib-scanner 2.10.23 → 2.10.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/bin/muaddib.js +2 -541
  2. package/package.json +1 -1
  3. package/src/canary-tokens.js +2 -256
  4. package/src/commands/diff.js +411 -0
  5. package/src/commands/help.js +356 -0
  6. package/src/commands/hooks-init.js +264 -0
  7. package/src/commands/interactive.js +200 -0
  8. package/src/commands/safe-install.js +294 -0
  9. package/src/diff.js +2 -411
  10. package/src/hooks-init.js +2 -264
  11. package/src/index.js +20 -861
  12. package/src/integrations/canary-tokens.js +256 -0
  13. package/src/integrations/maintainer-change.js +224 -0
  14. package/src/integrations/publish-anomaly.js +206 -0
  15. package/src/integrations/threat-feed.js +95 -0
  16. package/src/integrations/webhook.js +445 -0
  17. package/src/maintainer-change.js +2 -224
  18. package/src/ml/jsonl-writer.js +2 -1
  19. package/src/monitor/classify.js +355 -0
  20. package/src/monitor/daemon.js +200 -0
  21. package/src/monitor/healthcheck.js +147 -0
  22. package/src/monitor/ingestion.js +605 -0
  23. package/src/monitor/queue.js +994 -0
  24. package/src/monitor/state.js +881 -0
  25. package/src/monitor/temporal.js +433 -0
  26. package/src/monitor/webhook.js +1055 -0
  27. package/src/output/formatter.js +192 -0
  28. package/src/output/report.js +230 -0
  29. package/src/output/sarif.js +102 -0
  30. package/src/output-formatter.js +2 -192
  31. package/src/pipeline/executor.js +295 -0
  32. package/src/pipeline/initializer.js +62 -0
  33. package/src/pipeline/outputter.js +51 -0
  34. package/src/pipeline/processor.js +300 -0
  35. package/src/pipeline/scan-worker.js +31 -0
  36. package/src/publish-anomaly.js +2 -206
  37. package/src/report.js +2 -230
  38. package/src/runtime/daemon.js +178 -0
  39. package/src/runtime/serve.js +126 -0
  40. package/src/runtime/watch.js +56 -0
  41. package/src/safe-install.js +2 -294
  42. package/src/sarif.js +2 -102
  43. package/src/scan-context.js +31 -0
  44. package/src/scan-worker.js +2 -31
  45. package/src/scanner/ast-detectors/constants.js +247 -0
  46. package/src/scanner/ast-detectors/handle-assignment-expression.js +285 -0
  47. package/src/scanner/ast-detectors/handle-call-expression.js +1822 -0
  48. package/src/scanner/ast-detectors/handle-import-expression.js +46 -0
  49. package/src/scanner/ast-detectors/handle-literal.js +118 -0
  50. package/src/scanner/ast-detectors/handle-member-expression.js +80 -0
  51. package/src/scanner/ast-detectors/handle-new-expression.js +180 -0
  52. package/src/scanner/ast-detectors/handle-post-walk.js +494 -0
  53. package/src/scanner/ast-detectors/handle-variable-declarator.js +376 -0
  54. package/src/scanner/ast-detectors/handle-with-statement.js +55 -0
  55. package/src/scanner/ast-detectors/helpers.js +261 -0
  56. package/src/scanner/ast-detectors/index.js +23 -0
  57. package/src/scanner/ast.js +1 -1
  58. package/src/scanner/module-graph/annotate-sinks.js +188 -0
  59. package/src/scanner/module-graph/annotate-tainted.js +369 -0
  60. package/src/scanner/module-graph/build-graph.js +119 -0
  61. package/src/scanner/module-graph/constants.js +33 -0
  62. package/src/scanner/module-graph/detect-callback-flows.js +129 -0
  63. package/src/scanner/module-graph/detect-cross-file.js +957 -0
  64. package/src/scanner/module-graph/detect-event-flows.js +213 -0
  65. package/src/scanner/module-graph/index.js +18 -0
  66. package/src/scanner/module-graph/parse-utils.js +142 -0
  67. package/src/scanner/paranoid.js +230 -0
  68. package/src/scanner/reachability.js +1 -1
  69. package/src/scanner/temporal-analysis.js +349 -0
  70. package/src/scanner/temporal-ast-diff.js +334 -0
  71. package/src/scanner/temporal-runner.js +163 -0
  72. package/src/temporal-analysis.js +2 -349
  73. package/src/temporal-ast-diff.js +2 -334
  74. package/src/temporal-runner.js +2 -163
  75. package/src/watch.js +2 -56
  76. package/src/webhook.js +2 -445
  77. package/scripts/fix-permissions.sh +0 -24
  78. package/src/scanner/ast-detectors.js +0 -3797
  79. package/src/scanner/module-graph.js +0 -2096
package/bin/muaddib.js CHANGED
@@ -30,6 +30,8 @@ const { safeInstall } = require('../src/safe-install.js');
30
30
  const { buildSandboxImage, runSandbox, generateNetworkReport } = require('../src/sandbox/index.js');
31
31
  const { diff, showRefs } = require('../src/diff.js');
32
32
  const { initHooks, removeHooks } = require('../src/hooks-init.js');
33
+ const { showHelp, commandHelp } = require('../src/commands/help.js');
34
+ const { interactiveMenu } = require('../src/commands/interactive.js');
33
35
 
34
36
  const args = process.argv.slice(2);
35
37
  const command = args[0];
@@ -221,547 +223,6 @@ if (!jsonOutput && !sarifOutput && command !== 'feed' && command !== 'serve') {
221
223
  }
222
224
  }
223
225
 
224
- // Interactive menu
225
- async function interactiveMenu() {
226
- const { select, input, confirm } = await import('@inquirer/prompts');
227
-
228
- console.log(`
229
- ╔═══════════════════════════════════════════════╗
230
- ║ MUAD'DIB - npm & PyPI Supply Chain Hunter ║
231
- ║ "The worms must die." ║
232
- ╚═══════════════════════════════════════════════╝
233
- `);
234
-
235
- const action = await select({
236
- message: 'What do you want to do?',
237
- choices: [
238
- { name: 'Scan a project', value: 'scan' },
239
- { name: 'Scan with paranoid mode', value: 'scan-paranoid' },
240
- { name: 'Compare with previous version (diff)', value: 'diff' },
241
- { name: 'Install packages (safe)', value: 'install' },
242
- { name: 'Watch a project (real-time)', value: 'watch' },
243
- { name: 'Start daemon', value: 'daemon' },
244
- { name: 'Setup git hooks', value: 'init-hooks' },
245
- { name: 'Update IOCs', value: 'update' },
246
- { name: 'Scrape new IOCs', value: 'scrape' },
247
- { name: 'Sandbox analysis', value: 'sandbox' },
248
- { name: 'Threat feed (JSON)', value: 'feed' },
249
- { name: 'Threat feed server', value: 'serve' },
250
- { name: 'Quit', value: 'quit' }
251
- ]
252
- });
253
-
254
- if (action === 'quit') {
255
- console.log('Bye!');
256
- process.exit(0);
257
- }
258
-
259
- if (action === 'scan' || action === 'scan-paranoid') {
260
- const path = await input({
261
- message: 'Project path:',
262
- default: '.'
263
- });
264
-
265
- const outputFormat = await select({
266
- message: 'Output format:',
267
- choices: [
268
- { name: 'Console (default)', value: 'console' },
269
- { name: 'JSON', value: 'json' },
270
- { name: 'HTML', value: 'html' },
271
- { name: 'SARIF (GitHub Security)', value: 'sarif' }
272
- ]
273
- });
274
-
275
- const opts = {
276
- json: outputFormat === 'json',
277
- html: outputFormat === 'html' ? 'muaddib-report.html' : null,
278
- sarif: outputFormat === 'sarif' ? 'muaddib-results.sarif' : null,
279
- explain: true,
280
- failLevel: 'high',
281
- paranoid: action === 'scan-paranoid'
282
- };
283
-
284
- const exitCode = await run(path, opts);
285
- process.exit(exitCode);
286
- }
287
-
288
- if (action === 'install') {
289
- const pkgInput = await input({
290
- message: 'Package(s) to install (space-separated):'
291
- });
292
-
293
- const packages = pkgInput.split(' ').filter(p => p.trim());
294
- if (packages.length === 0) {
295
- console.log('No packages specified.');
296
- process.exit(1);
297
- }
298
-
299
- const result = await safeInstall(packages, {});
300
- process.exit(result.blocked ? 1 : 0);
301
- }
302
-
303
- if (action === 'watch') {
304
- const path = await input({
305
- message: 'Project path:',
306
- default: '.'
307
- });
308
- watch(path);
309
- }
310
-
311
- if (action === 'daemon') {
312
- const useWebhook = await confirm({
313
- message: 'Configure Discord/Slack webhook?',
314
- default: false
315
- });
316
-
317
- let webhook = null;
318
- if (useWebhook) {
319
- webhook = await input({
320
- message: 'Webhook URL:'
321
- });
322
- }
323
- const { startDaemon } = require('../src/daemon.js');
324
- startDaemon({ webhook });
325
- }
326
-
327
- if (action === 'update') {
328
- await updateIOCs();
329
- process.exit(0);
330
- }
331
-
332
- if (action === 'scrape') {
333
- const result = await runScraper();
334
- console.log(`[OK] ${result.added} new IOCs (total: ${result.total})`);
335
- process.exit(0);
336
- }
337
-
338
- if (action === 'sandbox') {
339
- const packageName = await input({
340
- message: 'Package name to analyze:'
341
- });
342
-
343
- if (!packageName.trim()) {
344
- console.log('No package specified.');
345
- process.exit(1);
346
- }
347
-
348
- const useStrict = await confirm({
349
- message: 'Enable strict mode? (blocks non-essential network)',
350
- default: false
351
- });
352
-
353
- await buildSandboxImage();
354
- const results = await runSandbox(packageName.trim(), { strict: useStrict });
355
- if (results.raw_report) {
356
- console.log(generateNetworkReport(results.raw_report));
357
- }
358
- process.exit(results.suspicious ? 1 : 0);
359
- }
360
-
361
- if (action === 'feed') {
362
- const { getFeed } = require('../src/threat-feed.js');
363
- const result = getFeed();
364
- console.log(JSON.stringify(result, null, 2));
365
- process.exit(0);
366
- }
367
-
368
- if (action === 'serve') {
369
- const { startServer } = require('../src/serve.js');
370
- startServer({ port: 3000 });
371
- // Server runs indefinitely
372
- }
373
-
374
- if (action === 'diff') {
375
- const baseRef = await input({
376
- message: 'Compare with (commit/tag/branch):',
377
- default: 'HEAD~1'
378
- });
379
-
380
- const projectPath = await input({
381
- message: 'Project path:',
382
- default: '.'
383
- });
384
-
385
- const exitCode = await diff(projectPath, baseRef, { explain: true });
386
- process.exit(exitCode);
387
- }
388
-
389
- if (action === 'init-hooks') {
390
- const hookMode = await select({
391
- message: 'Hook mode:',
392
- choices: [
393
- { name: 'Scan all threats', value: 'scan' },
394
- { name: 'Diff only (block only NEW threats)', value: 'diff' }
395
- ]
396
- });
397
-
398
- const hookType = await select({
399
- message: 'Hook system:',
400
- choices: [
401
- { name: 'Auto-detect', value: 'auto' },
402
- { name: 'Husky', value: 'husky' },
403
- { name: 'pre-commit framework', value: 'pre-commit' },
404
- { name: 'Native git hooks', value: 'git' }
405
- ]
406
- });
407
-
408
- await initHooks('.', { type: hookType, mode: hookMode });
409
- process.exit(0);
410
- }
411
- }
412
-
413
- const helpText = `
414
- MUAD'DIB - npm & PyPI Supply Chain Threat Hunter
415
-
416
- Usage:
417
- muaddib Interactive mode
418
- muaddib scan [path] [options] Scan a project
419
- muaddib diff <ref> [path] Compare threats with a previous version
420
- muaddib install <pkg> [options] Safe install (scan before install)
421
- muaddib watch [path] Watch in real-time
422
- muaddib sandbox <pkg> [options] Analyze in isolated Docker container
423
- muaddib sandbox-report <pkg> Sandbox + detailed network report
424
- muaddib evaluate [options] Run TPR/FPR/ADR evaluation suite
425
- muaddib monitor [options] Start real-time npm/PyPI monitor
426
- muaddib daemon [options] Start daemon
427
- muaddib init-hooks [options] Setup git pre-commit hooks
428
- muaddib remove-hooks [path] Remove MUAD'DIB git hooks
429
- muaddib update Update IOCs
430
- muaddib scrape Scrape new IOCs
431
- muaddib feed [options] Threat feed (JSON)
432
- muaddib serve [options] Threat feed HTTP server
433
- muaddib detections [options] View monitor detections
434
- muaddib stats [options] View scan statistics
435
- muaddib replay [options] Replay ground-truth attacks
436
- muaddib version Show version
437
-
438
- Scan Options:
439
- --json JSON output
440
- --html [file] HTML report (default: muaddib-report.html)
441
- --sarif [file] SARIF report (default: muaddib-results.sarif)
442
- --explain Detailed explanations with MITRE references
443
- --breakdown Show score breakdown by threat
444
- --fail-on [level] Exit code threshold (critical|high|medium|low, default: high)
445
- --webhook [url] Discord/Slack webhook (HTTPS only)
446
- --paranoid Ultra-strict detection mode
447
- --exclude [dir] Exclude directory from scan (repeatable)
448
- --config [file] Custom config file (.muaddibrc.json format)
449
- --entropy-threshold [n] Custom entropy threshold (0-8, default: 5.5)
450
- --no-deobfuscate Disable deobfuscation pre-processing
451
- --no-module-graph Disable cross-file dataflow analysis
452
- --no-reachability Disable entry-point reachability analysis
453
- --auto-sandbox Auto-trigger sandbox when static scan score >= 20 (requires Docker)
454
-
455
- Temporal Options (scan):
456
- --temporal Detect sudden lifecycle script changes
457
- --temporal-ast Detect sudden dangerous API additions via AST diff
458
- --temporal-publish Detect publish frequency anomalies
459
- --temporal-maintainer Detect maintainer changes
460
- --temporal-full All temporal analyses combined
461
-
462
- Diff Examples:
463
- muaddib diff HEAD~1 Compare with previous commit
464
- muaddib diff v1.2.0 Compare with tag
465
- muaddib diff main Compare with branch
466
- muaddib diff abc1234 ./myproject Compare specific commit
467
-
468
- Install Options:
469
- --save-dev, -D Install as dev dependency
470
- -g, --global Install globally
471
- --force Force install despite threats
472
-
473
- Sandbox Options:
474
- --local Analyze a local path instead of npm package
475
- --strict Block non-essential network access
476
- --no-canary Disable honey token injection
477
-
478
- Init-hooks Options:
479
- --type [auto|husky|pre-commit|git] Hook system (default: auto)
480
- --mode [scan|diff] scan=all threats, diff=new only
481
-
482
- Evaluate Options:
483
- --json JSON output
484
- --benign-limit [n] Limit benign packages to scan
485
- --refresh-benign Re-download benign packages
486
-
487
- Feed/Serve Options:
488
- --limit [n] Limit feed entries (default: 50)
489
- --severity [level] Filter by severity (CRITICAL|HIGH|MEDIUM|LOW)
490
- --since [date] Filter detections after date (ISO 8601)
491
- --port [n] HTTP server port (default: 3000)
492
-
493
- Monitor Options:
494
- --verbose Show detailed output
495
- --temporal --test <pkg> Test temporal analysis on a single package
496
- --temporal-ast --test <pkg> Test AST diff on a single package
497
-
498
- Detections Options:
499
- --json JSON output
500
- --stats Show aggregated detection statistics
501
-
502
- Stats Options:
503
- --json JSON output
504
- --daily Show daily breakdown (last 7 days)
505
-
506
- Replay Options:
507
- --verbose Show detailed findings per attack
508
- --json Machine-readable JSON output
509
- GT-NNN Replay single attack by ID
510
- `;
511
-
512
- // Per-command help texts for subcommand --help
513
- const commandHelp = {
514
- scan: `
515
- Usage: muaddib scan [path] [options]
516
-
517
- Scan a project for supply-chain threats.
518
-
519
- Arguments:
520
- path Target directory to scan (default: .)
521
-
522
- Options:
523
- --json JSON output
524
- --html [file] HTML report (default: muaddib-report.html)
525
- --sarif [file] SARIF report (default: muaddib-results.sarif)
526
- --explain Detailed explanations with MITRE references
527
- --breakdown Show score breakdown by threat
528
- --fail-on [level] Exit code threshold (critical|high|medium|low, default: high)
529
- --webhook [url] Discord/Slack webhook (HTTPS only)
530
- --paranoid Ultra-strict detection mode
531
- --exclude [dir] Exclude directory from scan (repeatable)
532
- --config [file] Custom config file (.muaddibrc.json format)
533
- --entropy-threshold [n] Custom entropy threshold (0-8, default: 5.5)
534
- --no-deobfuscate Disable deobfuscation pre-processing
535
- --no-module-graph Disable cross-file dataflow analysis
536
- --no-reachability Disable entry-point reachability analysis
537
- --auto-sandbox Auto-trigger sandbox when static scan score >= 20 (requires Docker)
538
- --temporal Detect sudden lifecycle script changes
539
- --temporal-ast Detect sudden dangerous API additions
540
- --temporal-publish Detect publish frequency anomalies
541
- --temporal-maintainer Detect maintainer changes
542
- --temporal-full All temporal analyses combined
543
-
544
- Examples:
545
- muaddib scan .
546
- muaddib scan ./my-project --explain --paranoid
547
- muaddib scan . --json > results.json
548
- muaddib scan . --html report.html --explain
549
- `,
550
- diff: `
551
- Usage: muaddib diff <ref> [path] [options]
552
-
553
- Compare threats between two versions of a project.
554
- Without <ref>, shows available tags and recent commits.
555
-
556
- Arguments:
557
- ref Commit hash, tag, or branch to compare with
558
- path Target directory (default: .)
559
-
560
- Options:
561
- --json JSON output
562
- --explain Detailed explanations
563
- --fail-on [level] Exit code threshold (critical|high|medium|low)
564
- --paranoid Ultra-strict detection mode
565
-
566
- Examples:
567
- muaddib diff HEAD~1
568
- muaddib diff v1.2.0
569
- muaddib diff main ./myproject
570
- `,
571
- install: `
572
- Usage: muaddib install <package> [<package>...] [options]
573
-
574
- Scan packages for threats before installing them.
575
-
576
- Options:
577
- --save-dev, -D Install as dev dependency
578
- -g, --global Install globally
579
- --force Force install despite detected threats
580
-
581
- Examples:
582
- muaddib install lodash
583
- muaddib install express morgan --save-dev
584
- muaddib install -g typescript
585
- `,
586
- sandbox: `
587
- Usage: muaddib sandbox <package-name|path> [options]
588
-
589
- Run dynamic analysis in an isolated Docker container.
590
- Requires Docker to be installed and running.
591
-
592
- Options:
593
- --local Analyze a local path instead of npm package
594
- --strict Block non-essential network access
595
- --no-canary Disable honey token injection
596
-
597
- Examples:
598
- muaddib sandbox suspicious-pkg
599
- muaddib sandbox ./local-pkg --local --strict
600
- `,
601
- 'sandbox-report': `
602
- Usage: muaddib sandbox-report <package-name|path> [options]
603
-
604
- Run sandbox analysis with detailed network traffic report.
605
- Same options as 'muaddib sandbox'.
606
- `,
607
- evaluate: `
608
- Usage: muaddib evaluate [options]
609
-
610
- Run the full TPR/FPR/ADR evaluation suite against ground truth,
611
- benign packages, and adversarial samples.
612
-
613
- Options:
614
- --json JSON output
615
- --benign-limit [n] Limit benign packages to scan
616
- --refresh-benign Re-download benign packages
617
-
618
- Examples:
619
- muaddib evaluate
620
- muaddib evaluate --json > eval-results.json
621
- `,
622
- monitor: `
623
- Usage: muaddib monitor [options]
624
-
625
- Start real-time monitoring of npm/PyPI registries for
626
- newly published malicious packages.
627
-
628
- Options:
629
- --verbose Show detailed output
630
- --temporal --test <pkg> Test temporal analysis on a single package
631
- --temporal-ast --test <pkg> Test AST diff on a single package
632
-
633
- Examples:
634
- muaddib monitor
635
- muaddib monitor --verbose
636
- muaddib monitor --temporal --test lodash
637
- `,
638
- feed: `
639
- Usage: muaddib feed [options]
640
-
641
- Output the threat detection feed as JSON.
642
-
643
- Options:
644
- --limit [n] Limit entries (default: 50)
645
- --severity [level] Filter by severity (CRITICAL|HIGH|MEDIUM|LOW)
646
- --since [date] Filter after date (ISO 8601)
647
-
648
- Examples:
649
- muaddib feed
650
- muaddib feed --severity CRITICAL --limit 10
651
- `,
652
- serve: `
653
- Usage: muaddib serve [options]
654
-
655
- Start an HTTP server serving the threat feed.
656
-
657
- Options:
658
- --port [n] Server port (default: 3000)
659
-
660
- Examples:
661
- muaddib serve
662
- muaddib serve --port 8080
663
- `,
664
- detections: `
665
- Usage: muaddib detections [options]
666
-
667
- View detections recorded by the monitor.
668
-
669
- Options:
670
- --json Full JSON output
671
- --stats Show aggregated detection statistics
672
-
673
- Examples:
674
- muaddib detections
675
- muaddib detections --stats
676
- muaddib detections --json > detections.json
677
- `,
678
- stats: `
679
- Usage: muaddib stats [options]
680
-
681
- View scan statistics from the monitor.
682
-
683
- Options:
684
- --json JSON output
685
- --daily Show daily breakdown (last 7 days)
686
-
687
- Examples:
688
- muaddib stats
689
- muaddib stats --daily
690
- `,
691
- replay: `
692
- Usage: muaddib replay [options] [GT-NNN]
693
-
694
- Replay ground-truth attack samples to verify detection.
695
-
696
- Options:
697
- --verbose, -v Show detailed findings per attack
698
- --json Machine-readable JSON output
699
- GT-NNN Replay a single attack by ID
700
-
701
- Examples:
702
- muaddib replay
703
- muaddib replay --verbose
704
- muaddib replay GT-001
705
- `,
706
- 'init-hooks': `
707
- Usage: muaddib init-hooks [options]
708
-
709
- Setup git pre-commit hooks for automatic scanning.
710
-
711
- Options:
712
- --type [auto|husky|pre-commit|git] Hook system (default: auto)
713
- --mode [scan|diff] scan=all threats, diff=new only
714
-
715
- Examples:
716
- muaddib init-hooks
717
- muaddib init-hooks --type husky --mode diff
718
- `,
719
- 'remove-hooks': `
720
- Usage: muaddib remove-hooks [path]
721
-
722
- Remove MUAD'DIB git hooks from a project.
723
- `,
724
- watch: `
725
- Usage: muaddib watch [path]
726
-
727
- Watch a project directory and re-scan on file changes.
728
-
729
- Examples:
730
- muaddib watch
731
- muaddib watch ./my-project
732
- `,
733
- daemon: `
734
- Usage: muaddib daemon [options]
735
-
736
- Start the MUAD'DIB daemon for background monitoring.
737
-
738
- Options:
739
- --webhook [url] Discord/Slack webhook URL
740
-
741
- Examples:
742
- muaddib daemon
743
- muaddib daemon --webhook https://discord.com/api/webhooks/...
744
- `,
745
- report: `
746
- Usage: muaddib report --now | --status
747
-
748
- Send or check status of daily monitor reports.
749
-
750
- Options:
751
- --now Send report immediately
752
- --status Show report status
753
- `,
754
- };
755
-
756
- // Show per-command help or global help
757
- function showHelp(cmd) {
758
- if (cmd && commandHelp[cmd]) {
759
- console.log(commandHelp[cmd]);
760
- } else {
761
- console.log(helpText);
762
- }
763
- process.exit(0);
764
- }
765
226
 
766
227
  // Main
767
228
  if (command === 'version' || command === '--version' || command === '-v') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.10.23",
3
+ "version": "2.10.28",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {