muaddib-scanner 2.10.21 → 2.10.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,7 @@
30
30
 
31
31
  npm and PyPI supply-chain attacks are exploding. Shai-Hulud compromised 25K+ repos in 2025. Existing tools detect threats but don't help you respond.
32
32
 
33
- MUAD'DIB combines **14 parallel scanners** (162 detection rules), a **deobfuscation engine**, **inter-module dataflow analysis**, **compound scoring**, **ML classifiers** (XGBoost), and Docker sandbox to detect known threats and suspicious behavioral patterns in npm and PyPI packages.
33
+ MUAD'DIB combines **14 parallel scanners** (176 detection rules), a **deobfuscation engine**, **inter-module dataflow analysis**, **compound scoring**, **ML classifiers** (XGBoost), and Docker sandbox to detect known threats and suspicious behavioral patterns in npm and PyPI packages.
34
34
 
35
35
  ---
36
36
 
@@ -136,7 +136,7 @@ Ultra-strict detection with lower tolerance. Detects any network access, subproc
136
136
  muaddib scan . --webhook "https://discord.com/api/webhooks/..."
137
137
  ```
138
138
 
139
- Strict filtering (v2.1.2): alerts only for IOC matches, sandbox-confirmed threats, or canary token exfiltration. Priority triage (v2.10.5): P1 (red, IOC/sandbox/canary), P2 (orange, high-score/compounds), P3 (yellow, rest).
139
+ Strict filtering (v2.1.2): alerts only for IOC matches, sandbox-confirmed threats, or canary token exfiltration. Priority triage (v2.10.21): P1 (red, IOC/sandbox/canary), P2 (orange, high-score/compounds), P3 (yellow, rest).
140
140
 
141
141
  ### Behavioral anomaly detection (v2.0)
142
142
 
@@ -195,9 +195,9 @@ muaddib replay # Ground truth validation (46/49 TPR)
195
195
  | GitHub Actions | Shai-Hulud backdoor detection |
196
196
  | Hash Scanner | Known malicious file hashes |
197
197
 
198
- ### 162 detection rules
198
+ ### 176 detection rules
199
199
 
200
- All rules are mapped to MITRE ATT&CK techniques. See [SECURITY.md](SECURITY.md#detection-rules-v2105) for the complete rules reference.
200
+ All rules are mapped to MITRE ATT&CK techniques. See [SECURITY.md](SECURITY.md#detection-rules-v21021) for the complete rules reference.
201
201
 
202
202
  ### Detected campaigns
203
203
 
@@ -271,7 +271,7 @@ With pre-commit framework:
271
271
  ```yaml
272
272
  repos:
273
273
  - repo: https://github.com/DNSZLSK/muad-dib
274
- rev: v2.6.6
274
+ rev: v2.10.21
275
275
  hooks:
276
276
  - id: muaddib-scan
277
277
  ```
@@ -284,11 +284,11 @@ repos:
284
284
  |--------|--------|---------|
285
285
  | **Wild TPR** (Datadog 17K) | **92.8%** (13,538/14,587 in-scope) | 17,922 packages. 3,335 skipped (no JS). By category: compromised_lib 97.8%, malicious_intent 92.1% |
286
286
  | **TPR** (Ground Truth) | **93.9%** (46/49) | 51 real attacks. 3 out-of-scope: browser-only |
287
- | **FPR** (Benign curated) | **11.0%** (58/529) | 529 npm packages, real source via `npm pack` |
287
+ | **FPR** (Benign curated) | **10.6%** (56/529) | 529 npm packages, real source via `npm pack` |
288
288
  | **FPR** (Benign random) | **7.5%** (15/200) | 200 random npm packages, stratified sampling |
289
- | **ADR** (Adversarial + Holdout) | **96.3%** (103/107) | 67 adversarial + 40 holdout (107 available on disk), global threshold=20 |
289
+ | **ADR** (Adversarial + Holdout) | **94.0%** (101/107) | 67 adversarial + 40 holdout (107 available on disk), global threshold=20 |
290
290
 
291
- **2643 tests** across 57 files. **162 rules** (157 RULES + 5 PARANOID).
291
+ **2793 tests** across 57 files. **176 rules** (171 RULES + 5 PARANOID).
292
292
 
293
293
  > **Methodology caveats:**
294
294
  > - TPR measured on 49 Node.js attack samples (3 browser-only excluded from 51 total)
@@ -329,11 +329,11 @@ npm test
329
329
 
330
330
  ### Testing
331
331
 
332
- - **2643 tests** across 57 modular test files
332
+ - **2793 tests** across 57 modular test files
333
333
  - **56 fuzz tests** - Malformed inputs, ReDoS, unicode, binary
334
334
  - **Datadog 17K benchmark** - 14,587 confirmed malware samples (in-scope)
335
335
  - **Ground truth validation** - 51 real-world attacks (93.9% TPR)
336
- - **False positive validation** - 11.0% FPR on 529 curated npm packages, 7.5% on 200 random
336
+ - **False positive validation** - 10.6% FPR on 529 curated npm packages, 7.5% on 200 random
337
337
 
338
338
  ---
339
339
 
@@ -351,7 +351,7 @@ npm test
351
351
  - [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) - Experimental protocol, holdout scores
352
352
  - [Threat Model](docs/threat-model.md) - What MUAD'DIB detects and doesn't detect
353
353
  - [Adversarial Evaluation](ADVERSARIAL.md) - Red team samples and ADR results
354
- - [Security Policy](SECURITY.md) - Detection rules reference (162 rules)
354
+ - [Security Policy](SECURITY.md) - Detection rules reference (176 rules)
355
355
  - [Security Audit](docs/SECURITY_AUDIT.md) - Bypass validation report
356
356
  - [FP Analysis](docs/EVALUATION.md) - Historical false positive analysis
357
357
 
package/bin/muaddib.js CHANGED
@@ -13,6 +13,9 @@ const args = process.argv.slice(2);
13
13
  const command = args[0];
14
14
  const options = args.slice(1);
15
15
 
16
+ // Helper: detect --help / -h in options
17
+ const wantHelp = options.includes('--help') || options.includes('-h');
18
+
16
19
  // Parse options
17
20
  let target = '.';
18
21
  let jsonOutput = false;
@@ -394,19 +397,45 @@ const helpText = `
394
397
  muaddib diff <ref> [path] Compare threats with a previous version
395
398
  muaddib install <pkg> [options] Safe install (scan before install)
396
399
  muaddib watch [path] Watch in real-time
400
+ muaddib sandbox <pkg> [options] Analyze in isolated Docker container
401
+ muaddib sandbox-report <pkg> Sandbox + detailed network report
402
+ muaddib evaluate [options] Run TPR/FPR/ADR evaluation suite
403
+ muaddib monitor [options] Start real-time npm/PyPI monitor
397
404
  muaddib daemon [options] Start daemon
398
405
  muaddib init-hooks [options] Setup git pre-commit hooks
399
406
  muaddib remove-hooks [path] Remove MUAD'DIB git hooks
400
407
  muaddib update Update IOCs
401
408
  muaddib scrape Scrape new IOCs
402
- muaddib sandbox <pkg> [--strict] [--no-canary] Analyze in isolated Docker container
403
- muaddib sandbox-report <pkg> Sandbox + detailed network report
409
+ muaddib feed [options] Threat feed (JSON)
410
+ muaddib serve [options] Threat feed HTTP server
411
+ muaddib detections [options] View monitor detections
412
+ muaddib stats [options] View scan statistics
413
+ muaddib replay [options] Replay ground-truth attacks
404
414
  muaddib version Show version
405
415
 
406
- Replay Options:
407
- --verbose Show detailed findings per attack
408
- --json Machine-readable JSON output
409
- GT-NNN Replay single attack by ID
416
+ Scan Options:
417
+ --json JSON output
418
+ --html [file] HTML report (default: muaddib-report.html)
419
+ --sarif [file] SARIF report (default: muaddib-results.sarif)
420
+ --explain Detailed explanations with MITRE references
421
+ --breakdown Show score breakdown by threat
422
+ --fail-on [level] Exit code threshold (critical|high|medium|low, default: high)
423
+ --webhook [url] Discord/Slack webhook (HTTPS only)
424
+ --paranoid Ultra-strict detection mode
425
+ --exclude [dir] Exclude directory from scan (repeatable)
426
+ --config [file] Custom config file (.muaddibrc.json format)
427
+ --entropy-threshold [n] Custom entropy threshold (0-8, default: 5.5)
428
+ --no-deobfuscate Disable deobfuscation pre-processing
429
+ --no-module-graph Disable cross-file dataflow analysis
430
+ --no-reachability Disable entry-point reachability analysis
431
+ --auto-sandbox Auto-trigger sandbox when static scan score >= 20 (requires Docker)
432
+
433
+ Temporal Options (scan):
434
+ --temporal Detect sudden lifecycle script changes
435
+ --temporal-ast Detect sudden dangerous API additions via AST diff
436
+ --temporal-publish Detect publish frequency anomalies
437
+ --temporal-maintainer Detect maintainer changes
438
+ --temporal-full All temporal analyses combined
410
439
 
411
440
  Diff Examples:
412
441
  muaddib diff HEAD~1 Compare with previous commit
@@ -414,40 +443,303 @@ const helpText = `
414
443
  muaddib diff main Compare with branch
415
444
  muaddib diff abc1234 ./myproject Compare specific commit
416
445
 
446
+ Install Options:
447
+ --save-dev, -D Install as dev dependency
448
+ -g, --global Install globally
449
+ --force Force install despite threats
450
+
451
+ Sandbox Options:
452
+ --local Analyze a local path instead of npm package
453
+ --strict Block non-essential network access
454
+ --no-canary Disable honey token injection
455
+
417
456
  Init-hooks Options:
418
457
  --type [auto|husky|pre-commit|git] Hook system (default: auto)
419
458
  --mode [scan|diff] scan=all threats, diff=new only
420
459
 
460
+ Evaluate Options:
461
+ --json JSON output
462
+ --benign-limit [n] Limit benign packages to scan
463
+ --refresh-benign Re-download benign packages
464
+
465
+ Feed/Serve Options:
466
+ --limit [n] Limit feed entries (default: 50)
467
+ --severity [level] Filter by severity (CRITICAL|HIGH|MEDIUM|LOW)
468
+ --since [date] Filter detections after date (ISO 8601)
469
+ --port [n] HTTP server port (default: 3000)
470
+
471
+ Monitor Options:
472
+ --verbose Show detailed output
473
+ --temporal --test <pkg> Test temporal analysis on a single package
474
+ --temporal-ast --test <pkg> Test AST diff on a single package
475
+
476
+ Detections Options:
477
+ --json JSON output
478
+ --stats Show aggregated detection statistics
479
+
480
+ Stats Options:
481
+ --json JSON output
482
+ --daily Show daily breakdown (last 7 days)
483
+
484
+ Replay Options:
485
+ --verbose Show detailed findings per attack
486
+ --json Machine-readable JSON output
487
+ GT-NNN Replay single attack by ID
488
+ `;
489
+
490
+ // Per-command help texts for subcommand --help
491
+ const commandHelp = {
492
+ scan: `
493
+ Usage: muaddib scan [path] [options]
494
+
495
+ Scan a project for supply-chain threats.
496
+
497
+ Arguments:
498
+ path Target directory to scan (default: .)
499
+
421
500
  Options:
422
501
  --json JSON output
423
- --html [file] HTML report
424
- --sarif [file] SARIF report (GitHub Security)
425
- --explain Detailed explanations
502
+ --html [file] HTML report (default: muaddib-report.html)
503
+ --sarif [file] SARIF report (default: muaddib-results.sarif)
504
+ --explain Detailed explanations with MITRE references
426
505
  --breakdown Show score breakdown by threat
427
- --fail-on [level] Fail level (critical|high|medium|low)
428
- --webhook [url] Discord/Slack webhook
429
- --paranoid Ultra-strict mode
430
- --temporal Detect sudden lifecycle script changes (network requests per package)
431
- --temporal-ast Detect sudden dangerous API additions via AST diff (downloads tarballs)
432
- --temporal-publish Detect publish frequency anomalies (bursts, dormant spikes)
433
- --temporal-maintainer Detect maintainer changes (new maintainer, account takeover)
434
- --temporal-full All temporal analyses (lifecycle + AST + publish + maintainer)
435
- --auto-sandbox Auto-trigger sandbox when static scan score >= 20 (requires Docker)
436
- --no-canary Disable honey token injection in sandbox
506
+ --fail-on [level] Exit code threshold (critical|high|medium|low, default: high)
507
+ --webhook [url] Discord/Slack webhook (HTTPS only)
508
+ --paranoid Ultra-strict detection mode
509
+ --exclude [dir] Exclude directory from scan (repeatable)
510
+ --config [file] Custom config file (.muaddibrc.json format)
511
+ --entropy-threshold [n] Custom entropy threshold (0-8, default: 5.5)
437
512
  --no-deobfuscate Disable deobfuscation pre-processing
438
513
  --no-module-graph Disable cross-file dataflow analysis
439
514
  --no-reachability Disable entry-point reachability analysis
440
- --exclude [dir] Exclude directory from scan (repeatable)
441
- --limit [n] Limit feed entries (default: 50)
442
- --severity [level] Filter by severity (CRITICAL|HIGH|MEDIUM|LOW)
443
- --since [date] Filter detections after date (ISO 8601)
444
- --port [n] HTTP server port (default: 3000, serve only)
445
- --entropy-threshold [n] Custom string-level entropy threshold (default: 5.5)
446
- --config [file] Custom config file (.muaddibrc.json format)
515
+ --auto-sandbox Auto-trigger sandbox when static scan score >= 20 (requires Docker)
516
+ --temporal Detect sudden lifecycle script changes
517
+ --temporal-ast Detect sudden dangerous API additions
518
+ --temporal-publish Detect publish frequency anomalies
519
+ --temporal-maintainer Detect maintainer changes
520
+ --temporal-full All temporal analyses combined
521
+
522
+ Examples:
523
+ muaddib scan .
524
+ muaddib scan ./my-project --explain --paranoid
525
+ muaddib scan . --json > results.json
526
+ muaddib scan . --html report.html --explain
527
+ `,
528
+ diff: `
529
+ Usage: muaddib diff <ref> [path] [options]
530
+
531
+ Compare threats between two versions of a project.
532
+ Without <ref>, shows available tags and recent commits.
533
+
534
+ Arguments:
535
+ ref Commit hash, tag, or branch to compare with
536
+ path Target directory (default: .)
537
+
538
+ Options:
539
+ --json JSON output
540
+ --explain Detailed explanations
541
+ --fail-on [level] Exit code threshold (critical|high|medium|low)
542
+ --paranoid Ultra-strict detection mode
543
+
544
+ Examples:
545
+ muaddib diff HEAD~1
546
+ muaddib diff v1.2.0
547
+ muaddib diff main ./myproject
548
+ `,
549
+ install: `
550
+ Usage: muaddib install <package> [<package>...] [options]
551
+
552
+ Scan packages for threats before installing them.
553
+
554
+ Options:
447
555
  --save-dev, -D Install as dev dependency
448
556
  -g, --global Install globally
449
- --force Force install despite threats
450
- `;
557
+ --force Force install despite detected threats
558
+
559
+ Examples:
560
+ muaddib install lodash
561
+ muaddib install express morgan --save-dev
562
+ muaddib install -g typescript
563
+ `,
564
+ sandbox: `
565
+ Usage: muaddib sandbox <package-name|path> [options]
566
+
567
+ Run dynamic analysis in an isolated Docker container.
568
+ Requires Docker to be installed and running.
569
+
570
+ Options:
571
+ --local Analyze a local path instead of npm package
572
+ --strict Block non-essential network access
573
+ --no-canary Disable honey token injection
574
+
575
+ Examples:
576
+ muaddib sandbox suspicious-pkg
577
+ muaddib sandbox ./local-pkg --local --strict
578
+ `,
579
+ 'sandbox-report': `
580
+ Usage: muaddib sandbox-report <package-name|path> [options]
581
+
582
+ Run sandbox analysis with detailed network traffic report.
583
+ Same options as 'muaddib sandbox'.
584
+ `,
585
+ evaluate: `
586
+ Usage: muaddib evaluate [options]
587
+
588
+ Run the full TPR/FPR/ADR evaluation suite against ground truth,
589
+ benign packages, and adversarial samples.
590
+
591
+ Options:
592
+ --json JSON output
593
+ --benign-limit [n] Limit benign packages to scan
594
+ --refresh-benign Re-download benign packages
595
+
596
+ Examples:
597
+ muaddib evaluate
598
+ muaddib evaluate --json > eval-results.json
599
+ `,
600
+ monitor: `
601
+ Usage: muaddib monitor [options]
602
+
603
+ Start real-time monitoring of npm/PyPI registries for
604
+ newly published malicious packages.
605
+
606
+ Options:
607
+ --verbose Show detailed output
608
+ --temporal --test <pkg> Test temporal analysis on a single package
609
+ --temporal-ast --test <pkg> Test AST diff on a single package
610
+
611
+ Examples:
612
+ muaddib monitor
613
+ muaddib monitor --verbose
614
+ muaddib monitor --temporal --test lodash
615
+ `,
616
+ feed: `
617
+ Usage: muaddib feed [options]
618
+
619
+ Output the threat detection feed as JSON.
620
+
621
+ Options:
622
+ --limit [n] Limit entries (default: 50)
623
+ --severity [level] Filter by severity (CRITICAL|HIGH|MEDIUM|LOW)
624
+ --since [date] Filter after date (ISO 8601)
625
+
626
+ Examples:
627
+ muaddib feed
628
+ muaddib feed --severity CRITICAL --limit 10
629
+ `,
630
+ serve: `
631
+ Usage: muaddib serve [options]
632
+
633
+ Start an HTTP server serving the threat feed.
634
+
635
+ Options:
636
+ --port [n] Server port (default: 3000)
637
+
638
+ Examples:
639
+ muaddib serve
640
+ muaddib serve --port 8080
641
+ `,
642
+ detections: `
643
+ Usage: muaddib detections [options]
644
+
645
+ View detections recorded by the monitor.
646
+
647
+ Options:
648
+ --json Full JSON output
649
+ --stats Show aggregated detection statistics
650
+
651
+ Examples:
652
+ muaddib detections
653
+ muaddib detections --stats
654
+ muaddib detections --json > detections.json
655
+ `,
656
+ stats: `
657
+ Usage: muaddib stats [options]
658
+
659
+ View scan statistics from the monitor.
660
+
661
+ Options:
662
+ --json JSON output
663
+ --daily Show daily breakdown (last 7 days)
664
+
665
+ Examples:
666
+ muaddib stats
667
+ muaddib stats --daily
668
+ `,
669
+ replay: `
670
+ Usage: muaddib replay [options] [GT-NNN]
671
+
672
+ Replay ground-truth attack samples to verify detection.
673
+
674
+ Options:
675
+ --verbose, -v Show detailed findings per attack
676
+ --json Machine-readable JSON output
677
+ GT-NNN Replay a single attack by ID
678
+
679
+ Examples:
680
+ muaddib replay
681
+ muaddib replay --verbose
682
+ muaddib replay GT-001
683
+ `,
684
+ 'init-hooks': `
685
+ Usage: muaddib init-hooks [options]
686
+
687
+ Setup git pre-commit hooks for automatic scanning.
688
+
689
+ Options:
690
+ --type [auto|husky|pre-commit|git] Hook system (default: auto)
691
+ --mode [scan|diff] scan=all threats, diff=new only
692
+
693
+ Examples:
694
+ muaddib init-hooks
695
+ muaddib init-hooks --type husky --mode diff
696
+ `,
697
+ 'remove-hooks': `
698
+ Usage: muaddib remove-hooks [path]
699
+
700
+ Remove MUAD'DIB git hooks from a project.
701
+ `,
702
+ watch: `
703
+ Usage: muaddib watch [path]
704
+
705
+ Watch a project directory and re-scan on file changes.
706
+
707
+ Examples:
708
+ muaddib watch
709
+ muaddib watch ./my-project
710
+ `,
711
+ daemon: `
712
+ Usage: muaddib daemon [options]
713
+
714
+ Start the MUAD'DIB daemon for background monitoring.
715
+
716
+ Options:
717
+ --webhook [url] Discord/Slack webhook URL
718
+
719
+ Examples:
720
+ muaddib daemon
721
+ muaddib daemon --webhook https://discord.com/api/webhooks/...
722
+ `,
723
+ report: `
724
+ Usage: muaddib report --now | --status
725
+
726
+ Send or check status of daily monitor reports.
727
+
728
+ Options:
729
+ --now Send report immediately
730
+ --status Show report status
731
+ `,
732
+ };
733
+
734
+ // Show per-command help or global help
735
+ function showHelp(cmd) {
736
+ if (cmd && commandHelp[cmd]) {
737
+ console.log(commandHelp[cmd]);
738
+ } else {
739
+ console.log(helpText);
740
+ }
741
+ process.exit(0);
742
+ }
451
743
 
452
744
  // Main
453
745
  if (command === 'version' || command === '--version' || command === '-v') {
@@ -456,18 +748,14 @@ if (command === 'version' || command === '--version' || command === '-v') {
456
748
  process.exit(0);
457
749
  } else if (!command || command === '--help' || command === '-h') {
458
750
  if (command === '--help' || command === '-h') {
459
- console.log(helpText);
460
- process.exit(0);
751
+ showHelp();
461
752
  }
462
753
  interactiveMenu().catch(err => {
463
754
  console.error('[ERROR]', err.message);
464
755
  process.exit(1);
465
756
  });
466
757
  } else if (command === 'scan') {
467
- if (options.includes('--help') || options.includes('-h')) {
468
- console.log(helpText);
469
- process.exit(0);
470
- }
758
+ if (wantHelp) showHelp('scan');
471
759
  run(target, {
472
760
  json: jsonOutput,
473
761
  html: htmlOutput,
@@ -495,6 +783,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
495
783
  process.exit(1);
496
784
  });
497
785
  } else if (command === 'feed') {
786
+ if (wantHelp) showHelp('feed');
498
787
  const { getFeed } = require('../src/threat-feed.js');
499
788
  const feedOpts = {};
500
789
  if (feedLimit) feedOpts.limit = feedLimit;
@@ -504,10 +793,12 @@ if (command === 'version' || command === '--version' || command === '-v') {
504
793
  console.log(JSON.stringify(result, null, 2));
505
794
  process.exit(0);
506
795
  } else if (command === 'serve') {
796
+ if (wantHelp) showHelp('serve');
507
797
  const { startServer } = require('../src/serve.js');
508
798
  startServer({ port: servePort || 3000 });
509
799
  // Server runs indefinitely — no process.exit
510
800
  } else if (command === 'watch') {
801
+ if (wantHelp) showHelp('watch');
511
802
  watch(target);
512
803
  } else if (command === 'update') {
513
804
  updateIOCs().then(() => {
@@ -525,6 +816,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
525
816
  process.exit(1);
526
817
  });
527
818
  } else if (command === 'monitor') {
819
+ if (wantHelp) showHelp('monitor');
528
820
  const testPkg = options.filter(o => !o.startsWith('-'));
529
821
  const isTemporal = options.includes('--temporal');
530
822
  const isTemporalAst = options.includes('--temporal-ast');
@@ -614,9 +906,11 @@ if (command === 'version' || command === '--version' || command === '-v') {
614
906
  });
615
907
  }
616
908
  } else if (command === 'daemon') {
909
+ if (wantHelp) showHelp('daemon');
617
910
  const { startDaemon } = require('../src/daemon.js');
618
911
  startDaemon({ webhook: webhookUrl });
619
912
  } else if (command === 'install' || command === 'i') {
913
+ if (wantHelp) showHelp('install');
620
914
  const packages = options.filter(o => !o.startsWith('-'));
621
915
  const isDev = options.includes('--save-dev') || options.includes('-D');
622
916
  const isGlobal = options.includes('-g') || options.includes('--global');
@@ -637,13 +931,14 @@ if (command === 'version' || command === '--version' || command === '-v') {
637
931
  process.exit(1);
638
932
  });
639
933
  } else if (command === 'sandbox') {
934
+ if (wantHelp) showHelp('sandbox');
640
935
  const sandboxOpts = options.filter(o => !o.startsWith('-'));
641
936
  const packageName = sandboxOpts[0];
642
937
  const strict = options.includes('--strict');
643
938
  const canary = !options.includes('--no-canary');
644
939
  const local = options.includes('--local');
645
940
  if (!packageName) {
646
- console.log('Usage: muaddib sandbox <package-name|path> [--local] [--strict] [--no-canary]');
941
+ console.log(commandHelp['sandbox']);
647
942
  process.exit(1);
648
943
  }
649
944
 
@@ -657,13 +952,14 @@ if (command === 'version' || command === '--version' || command === '-v') {
657
952
  process.exit(1);
658
953
  });
659
954
  } else if (command === 'sandbox-report') {
955
+ if (wantHelp) showHelp('sandbox-report');
660
956
  const sandboxOpts = options.filter(o => !o.startsWith('-'));
661
957
  const packageName = sandboxOpts[0];
662
958
  const strict = options.includes('--strict');
663
959
  const canary = !options.includes('--no-canary');
664
960
  const local = options.includes('--local');
665
961
  if (!packageName) {
666
- console.log('Usage: muaddib sandbox-report <package-name|path> [--local] [--strict] [--no-canary]');
962
+ console.log(commandHelp['sandbox-report']);
667
963
  process.exit(1);
668
964
  }
669
965
 
@@ -680,6 +976,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
680
976
  process.exit(1);
681
977
  });
682
978
  } else if (command === 'diff') {
979
+ if (wantHelp) showHelp('diff');
683
980
  // Parse diff arguments: muaddib diff <ref> [path] [options]
684
981
  const diffArgs = options.filter(o => !o.startsWith('-'));
685
982
  const baseRef = diffArgs[0];
@@ -702,6 +999,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
702
999
  process.exit(1);
703
1000
  });
704
1001
  } else if (command === 'detections') {
1002
+ if (wantHelp) showHelp('detections');
705
1003
  const { loadDetections, getDetectionStats } = require('../src/monitor.js');
706
1004
  const wantStats = options.includes('--stats');
707
1005
  const wantJson = options.includes('--json');
@@ -753,6 +1051,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
753
1051
  console.log('');
754
1052
  process.exit(0);
755
1053
  } else if (command === 'stats') {
1054
+ if (wantHelp) showHelp('stats');
756
1055
  const { loadScanStats } = require('../src/monitor.js');
757
1056
  const wantDaily = options.includes('--daily');
758
1057
  const wantJson = options.includes('--json');
@@ -796,6 +1095,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
796
1095
  console.log('');
797
1096
  process.exit(0);
798
1097
  } else if (command === 'evaluate') {
1098
+ if (wantHelp) showHelp('evaluate');
799
1099
  const { evaluate } = require('../src/commands/evaluate.js');
800
1100
  const evalOpts = { json: jsonOutput };
801
1101
  for (let i = 0; i < options.length; i++) {
@@ -813,6 +1113,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
813
1113
  process.exit(1);
814
1114
  });
815
1115
  } else if (command === 'init-hooks') {
1116
+ if (wantHelp) showHelp('init-hooks');
816
1117
  // Parse init-hooks arguments
817
1118
  let hookType = 'auto';
818
1119
  let hookMode = 'scan';
@@ -834,6 +1135,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
834
1135
  process.exit(1);
835
1136
  });
836
1137
  } else if (command === 'remove-hooks') {
1138
+ if (wantHelp) showHelp('remove-hooks');
837
1139
  removeHooks(target).then(success => {
838
1140
  process.exit(success ? 0 : 1);
839
1141
  }).catch(err => {
@@ -841,6 +1143,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
841
1143
  process.exit(1);
842
1144
  });
843
1145
  } else if (command === 'replay' || command === 'ground-truth') {
1146
+ if (wantHelp) showHelp('replay');
844
1147
  const { replay } = require('../tests/ground-truth/replay.js');
845
1148
  const replayOpts = {};
846
1149
  for (const o of options) {
@@ -858,7 +1161,7 @@ if (command === 'version' || command === '--version' || command === '-v') {
858
1161
  process.exit(1);
859
1162
  });
860
1163
  } else if (command === 'report') {
861
- // Hidden/internal — not in --help
1164
+ if (wantHelp) showHelp('report');
862
1165
  if (options.includes('--now')) {
863
1166
  const { sendReportNow } = require('../src/monitor.js');
864
1167
  sendReportNow().then(result => {
@@ -889,8 +1192,9 @@ if (command === 'version' || command === '--version' || command === '-v') {
889
1192
  process.exit(1);
890
1193
  }
891
1194
  } else if (command === 'help') {
892
- console.log(helpText);
893
- process.exit(0);
1195
+ // muaddib help <command> — show per-command help
1196
+ const helpCmd = options.filter(o => !o.startsWith('-'))[0];
1197
+ showHelp(helpCmd);
894
1198
  } else {
895
1199
  console.log(`Unknown command: ${String(command).replace(/[\x00-\x1f\x7f-\x9f]/g, '')}`);
896
1200
  console.log('Type "muaddib help" to see available commands.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.10.21",
3
+ "version": "2.10.22",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {