muaddib-scanner 2.2.9 → 2.2.11
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.fr.md +22 -7
- package/README.md +22 -7
- package/package.json +1 -1
- package/src/commands/evaluate.js +1 -1
- package/src/index.js +135 -27
package/README.fr.md
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
Les attaques supply-chain npm et PyPI explosent. Shai-Hulud a compromis 25K+ repos en 2025. Les outils existants détectent, mais n'aident pas à répondre.
|
|
32
32
|
|
|
33
|
-
MUAD'DIB combine analyse statique + **moteur de désobfuscation** (v2.2.5) + **dataflow inter-module** (v2.2.6) + analyse dynamique (sandbox Docker) + **détection comportementale d'anomalies** (v2.0) + **validation ground truth** (v2.1) pour détecter les menaces ET guider votre réponse — même avant leur apparition dans une base d'IOC.
|
|
33
|
+
MUAD'DIB combine analyse statique + **moteur de désobfuscation** (v2.2.5) + **dataflow inter-module** (v2.2.6) + **scoring per-file max** (v2.2.11) + analyse dynamique (sandbox Docker) + **détection comportementale d'anomalies** (v2.0) + **validation ground truth** (v2.1) pour détecter les menaces ET guider votre réponse — même avant leur apparition dans une base d'IOC.
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
@@ -646,7 +646,7 @@ Les alertes apparaissent dans Security > Code scanning alerts.
|
|
|
646
646
|
## Architecture
|
|
647
647
|
|
|
648
648
|
```
|
|
649
|
-
MUAD'DIB 2.2.
|
|
649
|
+
MUAD'DIB 2.2.11 Scanner
|
|
650
650
|
|
|
|
651
651
|
+-- IOC Match (225 000+ packages, JSON DB)
|
|
652
652
|
| +-- OSV.dev npm dump (200K+ entrées MAL-*)
|
|
@@ -698,6 +698,11 @@ MUAD'DIB 2.2.9 Scanner
|
|
|
698
698
|
| +-- Obfuscation dans dist/build → LOW
|
|
699
699
|
| +-- Filtrage env vars safe + préfixes
|
|
700
700
|
|
|
|
701
|
+
+-- Scoring Per-File Max (v2.2.11)
|
|
702
|
+
| +-- Score = max(scores_par_fichier) + score_package_level
|
|
703
|
+
| +-- Élimine l'accumulation de score sur de nombreux fichiers
|
|
704
|
+
| +-- Menaces package-level (lifecycle, typosquat, IOC) scorées séparément
|
|
705
|
+
|
|
|
701
706
|
+-- Paranoid Mode (ultra-strict)
|
|
702
707
|
+-- Docker Sandbox (analyse comportementale, capture réseau, canary tokens, CI-aware)
|
|
703
708
|
+-- Moniteur Zero-Day (interne : polling RSS npm + PyPI, alertes Discord, rapport quotidien)
|
|
@@ -719,11 +724,21 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
719
724
|
| Metrique | Resultat | Details |
|
|
720
725
|
|----------|----------|---------|
|
|
721
726
|
| **TPR** (Ground Truth) | **100%** (4/4) | Attaques reelles : event-stream, ua-parser-js, coa, node-ipc |
|
|
722
|
-
| **FPR** (
|
|
727
|
+
| **FPR** (Packages standard) | **6.2%** (18/290) | Packages avec <10 fichiers JS — librairies et outils typiques |
|
|
728
|
+
| **FPR** (Benign, global) | **13.1%** (69/527) | 529 packages npm, vrai code source via `npm pack`, seuil > 20 |
|
|
723
729
|
| **ADR** (Adversarial) | **100%** (35/35) | 35 samples evasifs sur 4 vagues red team |
|
|
724
730
|
| **Holdouts** (pre-tuning) | 40/40 pass | Tous les holdouts passent apres corrections |
|
|
725
731
|
|
|
726
|
-
**
|
|
732
|
+
**FPR par taille de package** — Le FPR correle lineairement avec la taille du package. Le scoring per-file max (v2.2.11) reduit significativement les FP sur les packages moyens/gros :
|
|
733
|
+
|
|
734
|
+
| Categorie | Packages | FP | FPR |
|
|
735
|
+
|-----------|----------|-----|-----|
|
|
736
|
+
| Petits (<10 fichiers JS) | 290 | 18 | **6.2%** |
|
|
737
|
+
| Moyens (10-50 fichiers JS) | 135 | 16 | 11.9% |
|
|
738
|
+
| Gros (50-100 fichiers JS) | 40 | 10 | 25.0% |
|
|
739
|
+
| Tres gros (100+ fichiers JS) | 62 | 25 | 40.3% |
|
|
740
|
+
|
|
741
|
+
**Progression FPR** : 0% (invalide, dirs vides, v2.2.0-v2.2.6) → 38% (premiere vraie mesure, v2.2.7) → 19.4% (v2.2.8) → 17.5% (v2.2.9) → **13.1%** (v2.2.11, scoring per-file max)
|
|
727
742
|
|
|
728
743
|
**Progression holdout** (scores pre-tuning, regles gelees) :
|
|
729
744
|
|
|
@@ -736,7 +751,7 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
736
751
|
| v5 | 50% (5/10) | Dataflow inter-module (nouveau scanner) |
|
|
737
752
|
|
|
738
753
|
- **TPR** (True Positive Rate) : taux de detection sur 4 attaques supply-chain reelles (event-stream, ua-parser-js, coa, node-ipc)
|
|
739
|
-
- **FPR** (False Positive Rate) : packages avec score > 20 sur 529 packages npm reels (code source scanne, pas des dirs vides)
|
|
754
|
+
- **FPR** (False Positive Rate) : packages avec score > 20 sur 529 packages npm reels (code source scanne, pas des dirs vides). Le 6.2% sur les packages standard (<10 fichiers JS, 290 packages) est la metrique la plus representative pour un usage typique — la plupart des packages npm sont petits.
|
|
740
755
|
- **ADR** (Adversarial Detection Rate) : taux de detection sur 35 samples malveillants evasifs sur 4 vagues red team
|
|
741
756
|
- **Holdout** (pre-tuning) : taux de detection sur 10 samples jamais vus avec regles gelees (mesure de generalisation)
|
|
742
757
|
|
|
@@ -776,13 +791,13 @@ npm test
|
|
|
776
791
|
|
|
777
792
|
### Tests
|
|
778
793
|
|
|
779
|
-
- **
|
|
794
|
+
- **836 tests unitaires/intégration** sur 20 fichiers modulaires - 74% coverage via [Codecov](https://codecov.io/gh/DNSZLSK/muad-dib)
|
|
780
795
|
- **56 tests de fuzzing** - YAML malformé, JSON invalide, fichiers binaires, ReDoS, unicode, inputs 10MB
|
|
781
796
|
- **35 samples adversariaux** - Packages malveillants évasifs, taux de détection 35/35 (100% ADR)
|
|
782
797
|
- **50 samples holdout** - 5 batches de 10, scores pre-tuning : 30% → 40% → 60% → 80% → 50%
|
|
783
798
|
- **8 tests multi-facteur typosquat** - Cas limites et comportement cache
|
|
784
799
|
- **Validation ground truth** - 5/5 attaques réelles détectées (event-stream, ua-parser-js, coa, node-ipc, colors)
|
|
785
|
-
- **Validation faux positifs** -
|
|
800
|
+
- **Validation faux positifs** - 6.2% FPR sur packages standard (18/290), 13.1% global (69/527) sur vrai code source npm via `npm pack`
|
|
786
801
|
- **Audit ESLint sécurité** - `eslint-plugin-security` avec 14 règles activées
|
|
787
802
|
|
|
788
803
|
---
|
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 static analysis + **deobfuscation engine** (v2.2.5) + **inter-module dataflow** (v2.2.6) + dynamic analysis (Docker sandbox) + **behavioral anomaly detection** (v2.0) + **ground truth validation** (v2.1) to detect threats AND guide your response — even before they appear in any IOC database.
|
|
33
|
+
MUAD'DIB combines static analysis + **deobfuscation engine** (v2.2.5) + **inter-module dataflow** (v2.2.6) + **per-file max scoring** (v2.2.11) + dynamic analysis (Docker sandbox) + **behavioral anomaly detection** (v2.0) + **ground truth validation** (v2.1) to detect threats AND guide your response — even before they appear in any IOC database.
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
@@ -647,7 +647,7 @@ Alerts appear in Security > Code scanning alerts.
|
|
|
647
647
|
## Architecture
|
|
648
648
|
|
|
649
649
|
```
|
|
650
|
-
MUAD'DIB 2.2.
|
|
650
|
+
MUAD'DIB 2.2.11 Scanner
|
|
651
651
|
|
|
|
652
652
|
+-- IOC Match (225,000+ packages, JSON DB)
|
|
653
653
|
| +-- OSV.dev npm dump (200K+ MAL-* entries)
|
|
@@ -701,6 +701,11 @@ MUAD'DIB 2.2.9 Scanner
|
|
|
701
701
|
| +-- Obfuscation in dist/build → LOW
|
|
702
702
|
| +-- Safe env var + prefix filtering
|
|
703
703
|
|
|
|
704
|
+
+-- Per-File Max Scoring (v2.2.11)
|
|
705
|
+
| +-- Score = max(file_scores) + package_level_score
|
|
706
|
+
| +-- Eliminates score accumulation across many files
|
|
707
|
+
| +-- Package-level threats (lifecycle, typosquat, IOC) scored separately
|
|
708
|
+
|
|
|
704
709
|
+-- Paranoid Mode (ultra-strict)
|
|
705
710
|
+-- Docker Sandbox (behavioral analysis, network capture, canary tokens, CI-aware)
|
|
706
711
|
+-- Zero-Day Monitor (internal: npm + PyPI RSS polling, Discord alerts, daily report)
|
|
@@ -722,11 +727,21 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
722
727
|
| Metric | Result | Details |
|
|
723
728
|
|--------|--------|---------|
|
|
724
729
|
| **TPR** (Ground Truth) | **100%** (4/4) | Real-world attacks: event-stream, ua-parser-js, coa, node-ipc |
|
|
725
|
-
| **FPR** (
|
|
730
|
+
| **FPR** (Standard packages) | **6.2%** (18/290) | Packages with <10 JS files — typical libraries and tools |
|
|
731
|
+
| **FPR** (Benign, global) | **13.1%** (69/527) | 529 npm packages, real source code via `npm pack`, threshold > 20 |
|
|
726
732
|
| **ADR** (Adversarial) | **100%** (35/35) | 35 evasive samples across 4 red-team waves |
|
|
727
733
|
| **Holdouts** (pre-tuning) | 40/40 pass | All holdout samples pass after corrections |
|
|
728
734
|
|
|
729
|
-
**FPR
|
|
735
|
+
**FPR by package size** — FPR correlates linearly with package size. Per-file max scoring (v2.2.11) significantly reduces FP on medium/large packages:
|
|
736
|
+
|
|
737
|
+
| Category | Packages | FP | FPR |
|
|
738
|
+
|----------|----------|-----|-----|
|
|
739
|
+
| Small (<10 JS files) | 290 | 18 | **6.2%** |
|
|
740
|
+
| Medium (10-50 JS files) | 135 | 16 | 11.9% |
|
|
741
|
+
| Large (50-100 JS files) | 40 | 10 | 25.0% |
|
|
742
|
+
| Very large (100+ JS files) | 62 | 25 | 40.3% |
|
|
743
|
+
|
|
744
|
+
**FPR progression**: 0% (invalid, empty dirs, v2.2.0-v2.2.6) → 38% (first real measurement, v2.2.7) → 19.4% (v2.2.8) → 17.5% (v2.2.9) → **13.1%** (v2.2.11, per-file max scoring)
|
|
730
745
|
|
|
731
746
|
**Holdout progression** (pre-tuning scores, rules frozen):
|
|
732
747
|
|
|
@@ -739,7 +754,7 @@ Output (CLI, JSON, HTML, SARIF, Webhook, Threat Feed)
|
|
|
739
754
|
| v5 | 50% (5/10) | Inter-module dataflow (new scanner) |
|
|
740
755
|
|
|
741
756
|
- **TPR** (True Positive Rate): detection rate on 4 real-world supply-chain attacks (event-stream, ua-parser-js, coa, node-ipc)
|
|
742
|
-
- **FPR** (False Positive Rate): packages scoring > 20 out of 529 real npm packages (source code scanned, not empty dirs)
|
|
757
|
+
- **FPR** (False Positive Rate): packages scoring > 20 out of 529 real npm packages (source code scanned, not empty dirs). The 6.2% on standard packages (<10 JS files, 290 packages) is the most representative metric for typical use — most npm packages are small.
|
|
743
758
|
- **ADR** (Adversarial Detection Rate): detection rate on 35 evasive malicious samples across 4 red-team waves
|
|
744
759
|
- **Holdout** (pre-tuning): detection rate on 10 unseen samples with rules frozen (measures generalization)
|
|
745
760
|
|
|
@@ -779,13 +794,13 @@ npm test
|
|
|
779
794
|
|
|
780
795
|
### Testing
|
|
781
796
|
|
|
782
|
-
- **
|
|
797
|
+
- **836 unit/integration tests** across 20 modular test files - 74% code coverage via [Codecov](https://codecov.io/gh/DNSZLSK/muad-dib)
|
|
783
798
|
- **56 fuzz tests** - Malformed YAML, invalid JSON, binary files, ReDoS, unicode, 10MB inputs
|
|
784
799
|
- **35 adversarial samples** - Evasive malicious packages, 35/35 detection rate (100% ADR)
|
|
785
800
|
- **50 holdout samples** - 5 batches of 10, pre-tuning scores: 30% → 40% → 60% → 80% → 50%
|
|
786
801
|
- **8 multi-factor typosquat tests** - Edge cases and cache behavior
|
|
787
802
|
- **Ground truth validation** - 5/5 real-world attacks detected (event-stream, ua-parser-js, coa, node-ipc, colors)
|
|
788
|
-
- **False positive validation** -
|
|
803
|
+
- **False positive validation** - 6.2% FPR on standard packages (18/290), 13.1% global (69/527) on real npm source code via `npm pack`
|
|
789
804
|
- **ESLint security audit** - `eslint-plugin-security` with 14 rules enabled
|
|
790
805
|
|
|
791
806
|
---
|
package/package.json
CHANGED
package/src/commands/evaluate.js
CHANGED
package/src/index.js
CHANGED
|
@@ -64,6 +64,67 @@ const MAX_RISK_SCORE = 100;
|
|
|
64
64
|
|
|
65
65
|
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
66
66
|
|
|
67
|
+
// Cap MEDIUM prototype_hook contribution (frameworks like Restify have 50+ extensions)
|
|
68
|
+
const PROTO_HOOK_MEDIUM_CAP = 15;
|
|
69
|
+
|
|
70
|
+
// ============================================
|
|
71
|
+
// PER-FILE MAX SCORING (v2.2.11)
|
|
72
|
+
// ============================================
|
|
73
|
+
// Threat types classified as package-level (not tied to a specific source file).
|
|
74
|
+
// These are added to the package score, not grouped by file.
|
|
75
|
+
const PACKAGE_LEVEL_TYPES = new Set([
|
|
76
|
+
'lifecycle_script', 'lifecycle_shell_pipe',
|
|
77
|
+
'lifecycle_added_critical', 'lifecycle_added_high', 'lifecycle_modified',
|
|
78
|
+
'known_malicious_package', 'typosquat_detected',
|
|
79
|
+
'shai_hulud_marker', 'suspicious_file',
|
|
80
|
+
'pypi_malicious_package', 'pypi_typosquat_detected',
|
|
81
|
+
'dangerous_api_added_critical', 'dangerous_api_added_high', 'dangerous_api_added_medium',
|
|
82
|
+
'publish_burst', 'publish_dormant_spike', 'publish_rapid_succession',
|
|
83
|
+
'maintainer_new_suspicious', 'maintainer_sole_change',
|
|
84
|
+
'sandbox_network_activity', 'sandbox_file_changes', 'sandbox_process_spawns',
|
|
85
|
+
'sandbox_canary_exfiltration'
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Classify a threat as package-level or file-level.
|
|
90
|
+
* Package-level: metadata findings (package.json, node_modules, sandbox)
|
|
91
|
+
* File-level: code-level findings in specific source files
|
|
92
|
+
*/
|
|
93
|
+
function isPackageLevelThreat(threat) {
|
|
94
|
+
if (PACKAGE_LEVEL_TYPES.has(threat.type)) return true;
|
|
95
|
+
if (threat.file === 'package.json') return true;
|
|
96
|
+
if (threat.file && (threat.file.startsWith('node_modules/') || threat.file.startsWith('node_modules\\'))) return true;
|
|
97
|
+
if (threat.file && threat.file.startsWith('[SANDBOX]')) return true;
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Compute a risk score for a group of threats using standard weights.
|
|
103
|
+
* Handles prototype_hook MEDIUM cap per group.
|
|
104
|
+
* @param {Array} threats - array of threat objects (after FP reductions)
|
|
105
|
+
* @returns {number} score 0-100
|
|
106
|
+
*/
|
|
107
|
+
function computeGroupScore(threats) {
|
|
108
|
+
const criticalCount = threats.filter(t => t.severity === 'CRITICAL').length;
|
|
109
|
+
const highCount = threats.filter(t => t.severity === 'HIGH').length;
|
|
110
|
+
const mediumCount = threats.filter(t => t.severity === 'MEDIUM').length;
|
|
111
|
+
const lowCount = threats.filter(t => t.severity === 'LOW').length;
|
|
112
|
+
|
|
113
|
+
const mediumProtoHookCount = threats.filter(
|
|
114
|
+
t => t.type === 'prototype_hook' && t.severity === 'MEDIUM'
|
|
115
|
+
).length;
|
|
116
|
+
const protoHookPoints = Math.min(mediumProtoHookCount * SEVERITY_WEIGHTS.MEDIUM, PROTO_HOOK_MEDIUM_CAP);
|
|
117
|
+
const otherMediumCount = mediumCount - mediumProtoHookCount;
|
|
118
|
+
|
|
119
|
+
let score = 0;
|
|
120
|
+
score += criticalCount * SEVERITY_WEIGHTS.CRITICAL;
|
|
121
|
+
score += highCount * SEVERITY_WEIGHTS.HIGH;
|
|
122
|
+
score += otherMediumCount * SEVERITY_WEIGHTS.MEDIUM;
|
|
123
|
+
score += protoHookPoints;
|
|
124
|
+
score += lowCount * SEVERITY_WEIGHTS.LOW;
|
|
125
|
+
return Math.min(MAX_RISK_SCORE, score);
|
|
126
|
+
}
|
|
127
|
+
|
|
67
128
|
// ============================================
|
|
68
129
|
// FP REDUCTION POST-PROCESSING
|
|
69
130
|
// ============================================
|
|
@@ -634,27 +695,57 @@ async function run(targetPath, options = {}) {
|
|
|
634
695
|
.map(t => ({ rule: t.rule_id, type: t.type, points: t.points, reason: t.message }))
|
|
635
696
|
.sort((a, b) => b.points - a.points);
|
|
636
697
|
|
|
637
|
-
//
|
|
698
|
+
// ============================================
|
|
699
|
+
// PER-FILE MAX SCORING (v2.2.11)
|
|
700
|
+
// ============================================
|
|
701
|
+
|
|
702
|
+
// 1. Separate deduped threats into package-level and file-level
|
|
703
|
+
const packageLevelThreats = [];
|
|
704
|
+
const fileLevelThreats = [];
|
|
705
|
+
for (const t of deduped) {
|
|
706
|
+
if (isPackageLevelThreat(t)) {
|
|
707
|
+
packageLevelThreats.push(t);
|
|
708
|
+
} else {
|
|
709
|
+
fileLevelThreats.push(t);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// 2. Group file-level threats by file
|
|
714
|
+
const fileGroups = new Map();
|
|
715
|
+
for (const t of fileLevelThreats) {
|
|
716
|
+
const key = t.file || '(unknown)';
|
|
717
|
+
if (!fileGroups.has(key)) fileGroups.set(key, []);
|
|
718
|
+
fileGroups.get(key).push(t);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// 3. Compute per-file scores and find the most suspicious file
|
|
722
|
+
let maxFileScore = 0;
|
|
723
|
+
let mostSuspiciousFile = null;
|
|
724
|
+
const fileScores = {};
|
|
725
|
+
for (const [file, fileThreats] of fileGroups) {
|
|
726
|
+
const score = computeGroupScore(fileThreats);
|
|
727
|
+
fileScores[file] = score;
|
|
728
|
+
if (score > maxFileScore) {
|
|
729
|
+
maxFileScore = score;
|
|
730
|
+
mostSuspiciousFile = file;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// 4. Compute package-level score (typosquat, lifecycle, dependency IOC, etc.)
|
|
735
|
+
const packageScore = computeGroupScore(packageLevelThreats);
|
|
736
|
+
|
|
737
|
+
// 5. Final score = max file score + package-level score, capped at 100
|
|
738
|
+
const riskScore = Math.min(MAX_RISK_SCORE, maxFileScore + packageScore);
|
|
739
|
+
|
|
740
|
+
// 6. Old global score for comparison (sum of ALL findings)
|
|
741
|
+
const globalRiskScore = computeGroupScore(deduped);
|
|
742
|
+
|
|
743
|
+
// 7. Severity counts (global, for summary display)
|
|
638
744
|
const criticalCount = deduped.filter(t => t.severity === 'CRITICAL').length;
|
|
639
745
|
const highCount = deduped.filter(t => t.severity === 'HIGH').length;
|
|
640
746
|
const mediumCount = deduped.filter(t => t.severity === 'MEDIUM').length;
|
|
641
747
|
const lowCount = deduped.filter(t => t.severity === 'LOW').length;
|
|
642
748
|
|
|
643
|
-
// Cap MEDIUM prototype_hook contribution to 15 points max (5 × MEDIUM=3)
|
|
644
|
-
// Frameworks like Restify have 50+ prototype extensions that are not malicious
|
|
645
|
-
const mediumProtoHookCount = deduped.filter(t => t.type === 'prototype_hook' && t.severity === 'MEDIUM').length;
|
|
646
|
-
const PROTO_HOOK_MEDIUM_CAP = 15;
|
|
647
|
-
const protoHookPoints = Math.min(mediumProtoHookCount * SEVERITY_WEIGHTS.MEDIUM, PROTO_HOOK_MEDIUM_CAP);
|
|
648
|
-
const otherMediumCount = mediumCount - mediumProtoHookCount;
|
|
649
|
-
|
|
650
|
-
let riskScore = 0;
|
|
651
|
-
riskScore += criticalCount * SEVERITY_WEIGHTS.CRITICAL;
|
|
652
|
-
riskScore += highCount * SEVERITY_WEIGHTS.HIGH;
|
|
653
|
-
riskScore += otherMediumCount * SEVERITY_WEIGHTS.MEDIUM;
|
|
654
|
-
riskScore += protoHookPoints;
|
|
655
|
-
riskScore += lowCount * SEVERITY_WEIGHTS.LOW;
|
|
656
|
-
riskScore = Math.min(MAX_RISK_SCORE, riskScore);
|
|
657
|
-
|
|
658
749
|
const riskLevel = riskScore >= RISK_THRESHOLDS.CRITICAL ? 'CRITICAL'
|
|
659
750
|
: riskScore >= RISK_THRESHOLDS.HIGH ? 'HIGH'
|
|
660
751
|
: riskScore >= RISK_THRESHOLDS.MEDIUM ? 'MEDIUM'
|
|
@@ -679,8 +770,13 @@ async function run(targetPath, options = {}) {
|
|
|
679
770
|
high: highCount,
|
|
680
771
|
medium: mediumCount,
|
|
681
772
|
low: lowCount,
|
|
682
|
-
riskScore
|
|
683
|
-
riskLevel
|
|
773
|
+
riskScore,
|
|
774
|
+
riskLevel,
|
|
775
|
+
globalRiskScore,
|
|
776
|
+
maxFileScore,
|
|
777
|
+
packageScore,
|
|
778
|
+
mostSuspiciousFile,
|
|
779
|
+
fileScores,
|
|
684
780
|
breakdown
|
|
685
781
|
},
|
|
686
782
|
sandbox: sandboxData
|
|
@@ -712,7 +808,14 @@ async function run(targetPath, options = {}) {
|
|
|
712
808
|
else console.log('');
|
|
713
809
|
|
|
714
810
|
const explainScoreBar = '█'.repeat(Math.floor(result.summary.riskScore / 5)) + '░'.repeat(20 - Math.floor(result.summary.riskScore / 5));
|
|
715
|
-
console.log(`[SCORE] ${result.summary.riskScore}/100 [${explainScoreBar}] ${result.summary.riskLevel}
|
|
811
|
+
console.log(`[SCORE] ${result.summary.riskScore}/100 [${explainScoreBar}] ${result.summary.riskLevel}`);
|
|
812
|
+
if (mostSuspiciousFile) {
|
|
813
|
+
console.log(` Max file: ${mostSuspiciousFile} (${maxFileScore} pts)`);
|
|
814
|
+
if (packageScore > 0) {
|
|
815
|
+
console.log(` Package-level: +${packageScore} pts`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
console.log('');
|
|
716
819
|
|
|
717
820
|
if (options.breakdown && breakdown.length > 0) {
|
|
718
821
|
console.log('[BREAKDOWN] Score contributors:');
|
|
@@ -720,10 +823,9 @@ async function run(targetPath, options = {}) {
|
|
|
720
823
|
const pts = String(entry.points).padStart(2);
|
|
721
824
|
console.log(` +${pts} ${entry.reason} (${entry.rule})`);
|
|
722
825
|
}
|
|
723
|
-
|
|
724
|
-
if (uncapped > MAX_RISK_SCORE) {
|
|
826
|
+
if (globalRiskScore !== riskScore) {
|
|
725
827
|
console.log(' ----');
|
|
726
|
-
console.log(`
|
|
828
|
+
console.log(` Global sum: ${globalRiskScore}, Per-file max: ${riskScore}`);
|
|
727
829
|
}
|
|
728
830
|
console.log('');
|
|
729
831
|
}
|
|
@@ -782,7 +884,14 @@ async function run(targetPath, options = {}) {
|
|
|
782
884
|
else console.log('');
|
|
783
885
|
|
|
784
886
|
const scoreBar = '█'.repeat(Math.floor(result.summary.riskScore / 5)) + '░'.repeat(20 - Math.floor(result.summary.riskScore / 5));
|
|
785
|
-
console.log(`[SCORE] ${result.summary.riskScore}/100 [${scoreBar}] ${result.summary.riskLevel}
|
|
887
|
+
console.log(`[SCORE] ${result.summary.riskScore}/100 [${scoreBar}] ${result.summary.riskLevel}`);
|
|
888
|
+
if (mostSuspiciousFile) {
|
|
889
|
+
console.log(` Max file: ${mostSuspiciousFile} (${maxFileScore} pts)`);
|
|
890
|
+
if (packageScore > 0) {
|
|
891
|
+
console.log(` Package-level: +${packageScore} pts`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
console.log('');
|
|
786
895
|
|
|
787
896
|
if (options.breakdown && breakdown.length > 0) {
|
|
788
897
|
console.log('[BREAKDOWN] Score contributors:');
|
|
@@ -790,10 +899,9 @@ async function run(targetPath, options = {}) {
|
|
|
790
899
|
const pts = String(entry.points).padStart(2);
|
|
791
900
|
console.log(` +${pts} ${entry.reason} (${entry.rule})`);
|
|
792
901
|
}
|
|
793
|
-
|
|
794
|
-
if (uncapped > MAX_RISK_SCORE) {
|
|
902
|
+
if (globalRiskScore !== riskScore) {
|
|
795
903
|
console.log(' ----');
|
|
796
|
-
console.log(`
|
|
904
|
+
console.log(` Global sum: ${globalRiskScore}, Per-file max: ${riskScore}`);
|
|
797
905
|
}
|
|
798
906
|
console.log('');
|
|
799
907
|
}
|
|
@@ -869,4 +977,4 @@ async function run(targetPath, options = {}) {
|
|
|
869
977
|
return Math.min(failingThreats.length, 125);
|
|
870
978
|
}
|
|
871
979
|
|
|
872
|
-
module.exports = { run };
|
|
980
|
+
module.exports = { run, isPackageLevelThreat, computeGroupScore };
|