muaddib-scanner 2.8.7 → 2.8.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.8.7",
3
+ "version": "2.8.8",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -44,7 +44,7 @@
44
44
  "node": ">=18.0.0"
45
45
  },
46
46
  "dependencies": {
47
- "@inquirer/prompts": "8.3.0",
47
+ "@inquirer/prompts": "8.3.2",
48
48
  "acorn": "8.16.0",
49
49
  "acorn-walk": "8.3.5",
50
50
  "adm-zip": "0.5.16",
package/src/index.js CHANGED
@@ -567,6 +567,33 @@ async function run(targetPath, options = {}) {
567
567
  }
568
568
  } catch { /* graceful fallback */ }
569
569
 
570
+ // Cross-scanner compound: detached_process + suspicious_dataflow in same file
571
+ // Catches cases where credential flow is detected by dataflow scanner, not AST scanner
572
+ {
573
+ const fileMap = Object.create(null);
574
+ for (const t of deduped) {
575
+ if (t.file) {
576
+ if (!fileMap[t.file]) fileMap[t.file] = [];
577
+ fileMap[t.file].push(t);
578
+ }
579
+ }
580
+ for (const file of Object.keys(fileMap)) {
581
+ const fileThreats = fileMap[file];
582
+ const hasDetached = fileThreats.some(t => t.type === 'detached_process');
583
+ const hasCredFlow = fileThreats.some(t => t.type === 'suspicious_dataflow');
584
+ const alreadyCompound = fileThreats.some(t => t.type === 'detached_credential_exfil');
585
+ if (hasDetached && hasCredFlow && !alreadyCompound) {
586
+ deduped.push({
587
+ type: 'detached_credential_exfil',
588
+ severity: 'CRITICAL',
589
+ message: 'Detached process + credential dataflow — background exfiltration (cross-scanner compound).',
590
+ file,
591
+ count: 1
592
+ });
593
+ }
594
+ }
595
+ }
596
+
570
597
  // FP reduction: legitimate frameworks produce high volumes of certain threat types.
571
598
  // A malware package typically has 1-3 occurrences, not dozens.
572
599
  applyFPReductions(deduped, reachableFiles, packageName, packageDeps);
@@ -134,9 +134,15 @@ function getStats() {
134
134
  *
135
135
  * @param {string} packageName - package name to relabel
136
136
  * @param {string} newLabel - 'fp' or 'confirmed'
137
+ * @param {number} [sandboxFindingCount] - number of sandbox findings (defense-in-depth for 'confirmed')
137
138
  * @returns {number} number of records updated
138
139
  */
139
- function relabelRecords(packageName, newLabel) {
140
+ function relabelRecords(packageName, newLabel, sandboxFindingCount) {
141
+ // Defense-in-depth: never write 'confirmed' without real sandbox findings
142
+ if (newLabel === 'confirmed' && (!sandboxFindingCount || sandboxFindingCount === 0)) {
143
+ console.warn(`[ML] BLOCKED relabel to 'confirmed' for ${packageName}: sandbox_finding_count=${sandboxFindingCount || 0}`);
144
+ return 0;
145
+ }
140
146
  try {
141
147
  if (!fs.existsSync(TRAINING_FILE)) return 0;
142
148
  const content = fs.readFileSync(TRAINING_FILE, 'utf8');
@@ -501,6 +501,10 @@ const PLAYBOOKS = {
501
501
  'CRITIQUE: Un Proxy JavaScript avec trap set/get/apply est combine avec un appel reseau. ' +
502
502
  'Technique d\'interception: le Proxy capture toutes les ecritures de proprietes (credentials, tokens, config) ' +
503
503
  'et les exfiltre via HTTPS/fetch/dgram. Supprimer le package. Auditer tous les modules qui importent ce package.',
504
+ detached_credential_exfil:
505
+ 'CRITIQUE: Process detache avec acces aux credentials et exfiltration reseau. ' +
506
+ 'Technique DPRK/Lazarus: le process fils survit au parent (detached:true, unref()) et exfiltre des secrets en arriere-plan. ' +
507
+ 'Supprimer le package immediatement. Regenerer tous les tokens/credentials. Auditer les process en cours d\'execution.',
504
508
  intent_credential_exfil:
505
509
  'CRITIQUE: Coherence d\'intention detectee — lecture de credentials combinee avec exfiltration reseau. ' +
506
510
  'Pattern multi-fichier DPRK/Lazarus: chaque fichier semble legitime individuellement mais le package ' +
@@ -1408,6 +1408,18 @@ const RULES = {
1408
1408
  },
1409
1409
 
1410
1410
  // Intent Graph rules (v2.6.0)
1411
+ detached_credential_exfil: {
1412
+ id: 'MUADDIB-AST-047',
1413
+ name: 'Detached Process Credential Exfiltration',
1414
+ severity: 'CRITICAL',
1415
+ confidence: 'high',
1416
+ description: 'Process detache (survit au parent) avec acces aux credentials et appel reseau — technique DPRK/Lazarus pour exfiltrer des secrets en arriere-plan',
1417
+ references: [
1418
+ 'https://attack.mitre.org/techniques/T1041/',
1419
+ 'https://www.cisa.gov/news-events/cybersecurity-advisories/aa22-108a'
1420
+ ],
1421
+ mitre: 'T1041'
1422
+ },
1411
1423
  intent_credential_exfil: {
1412
1424
  id: 'MUADDIB-INTENT-001',
1413
1425
  name: 'Intent Credential Exfiltration',
@@ -2187,6 +2187,24 @@ function handlePostWalk(ctx) {
2187
2187
  file: ctx.relFile
2188
2188
  });
2189
2189
  }
2190
+
2191
+ // DPRK/Lazarus compound: detached background process + credential env access + network
2192
+ // Pattern: spawn({detached:true}) reads secrets then exfils via network.
2193
+ // This combination is never legitimate — daemons don't read API keys and send them out.
2194
+ const hasDetachedInFile = ctx.threats.some(t =>
2195
+ t.file === ctx.relFile && t.type === 'detached_process'
2196
+ );
2197
+ const hasSensitiveEnvInFile = ctx.threats.some(t =>
2198
+ t.file === ctx.relFile && t.type === 'env_access'
2199
+ );
2200
+ if (hasDetachedInFile && hasSensitiveEnvInFile && ctx.hasNetworkCallInFile) {
2201
+ ctx.threats.push({
2202
+ type: 'detached_credential_exfil',
2203
+ severity: 'CRITICAL',
2204
+ message: 'Detached process + sensitive env access + network call — credential exfiltration via background process (DPRK/Lazarus evasion pattern).',
2205
+ file: ctx.relFile
2206
+ });
2207
+ }
2190
2208
  }
2191
2209
 
2192
2210
  function handleWithStatement(node, ctx) {
package/src/scoring.js CHANGED
@@ -153,7 +153,8 @@ const DIST_EXEMPT_TYPES = new Set([
153
153
  'download_exec_binary', // download + chmod + exec (binary dropper)
154
154
  'cross_file_dataflow', // credential read → network exfil across files
155
155
  'staged_eval_decode', // eval(atob(...)) (explicit payload staging)
156
- 'reverse_shell' // net.Socket + connect + pipe (always malicious)
156
+ 'reverse_shell', // net.Socket + connect + pipe (always malicious)
157
+ 'detached_credential_exfil' // detached process + credential exfil (DPRK/Lazarus)
157
158
  // P6: remote_code_load and proxy_data_intercept removed — in bundled dist/ files,
158
159
  // fetch + eval co-occurrence is coincidental (bundler combines HTTP client + template compilation).
159
160
  // fetch_decrypt_exec (fetch+decrypt+eval triple) remains exempt — never coincidental.
@@ -196,7 +197,8 @@ const REACHABILITY_EXEMPT_TYPES = new Set([
196
197
  'cross_file_dataflow',
197
198
  'typosquat_detected', 'pypi_typosquat_detected',
198
199
  'pypi_malicious_package',
199
- 'ai_config_injection', 'ai_config_injection_compound'
200
+ 'ai_config_injection', 'ai_config_injection_compound',
201
+ 'detached_credential_exfil' // DPRK/Lazarus: invoked via lifecycle, not require/import
200
202
  ]);
201
203
 
202
204
  // Custom class prototypes that HTTP frameworks legitimately extend.