muaddib-scanner 2.10.91 → 2.10.93

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
@@ -292,7 +292,7 @@ repos:
292
292
  | **FPR** (Benign random) | **7.5%** (15/200) | 200 random npm packages, stratified sampling |
293
293
  | **ADR** (Adversarial + Holdout) | **96.3%** (103/107) | 67 adversarial + 40 holdout (107 available on disk), global threshold=20 |
294
294
 
295
- **3134 tests** across 66 files. **200 rules** (195 RULES + 5 PARANOID).
295
+ **3230 tests** across 66 files. **207 rules** (202 RULES + 5 PARANOID).
296
296
 
297
297
  > **ML retrain methodology (v2.10.51):**
298
298
  > - Ground truth: 377 confirmed_malicious via auto-labeler (OSSF malicious-packages, GitHub Advisory Database, npm registry takedown correlation)
@@ -340,7 +340,7 @@ npm test
340
340
 
341
341
  ### Testing
342
342
 
343
- - **3134 tests** across 66 modular test files
343
+ - **3230 tests** across 66 modular test files
344
344
  - **56 fuzz tests** - Malformed inputs, ReDoS, unicode, binary
345
345
  - **Datadog 17K benchmark** - 14,587 confirmed malware samples (in-scope)
346
346
  - **Ground truth validation** - 67 real-world attacks (93.75% TPR@3, 85.9% TPR@20)
@@ -362,7 +362,7 @@ npm test
362
362
  - [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) - Experimental protocol, holdout scores
363
363
  - [Threat Model](docs/threat-model.md) - What MUAD'DIB detects and doesn't detect
364
364
  - [Adversarial Evaluation](ADVERSARIAL.md) - Red team samples and ADR results
365
- - [Security Policy](SECURITY.md) - Detection rules reference (200 rules)
365
+ - [Security Policy](SECURITY.md) - Detection rules reference (207 rules)
366
366
  - [Security Audit](docs/SECURITY_AUDIT.md) - Bypass validation report
367
367
  - [FP Analysis](docs/EVALUATION.md) - Historical false positive analysis
368
368
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.10.91",
3
+ "version": "2.10.93",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -57,7 +57,13 @@ const HIGH_CONFIDENCE_MALICE_TYPES = new Set([
57
57
  'npm_token_steal', // exec("npm config get _authToken") (CanisterWorm findNpmTokens)
58
58
  'root_filesystem_wipe', // rm -rf / (CanisterWorm kamikaze.sh wiper T1485)
59
59
  'proc_mem_scan', // /proc/mem scanning (TeamPCP Trivy credential stealer)
60
- 'trusted_new_unknown_dependency' // TRUSTED package added unknown/new (<7d) dependency (account takeover)
60
+ 'trusted_new_unknown_dependency', // TRUSTED package added unknown/new (<7d) dependency (account takeover)
61
+ // v2.10.89: Security review findings — always malicious regardless of lifecycle
62
+ 'curl_env_exfil', // curl/wget + env/base64 in lifecycle (exfiltration)
63
+ 'function_constructor_require', // new Function.constructor("require") (RCE evasion)
64
+ 'newsletter_auto_follow', // Baileys WhatsApp newsletter hijack
65
+ // v2.10.93: Security review 2026-04-10→17 findings
66
+ 'self_destruct_eval' // dynamic exec + unlink __filename (csec anti-forensics)
61
67
  ]);
62
68
 
63
69
  // Lifecycle compound types that indicate real malicious intent beyond a simple postinstall
@@ -881,6 +881,11 @@ const PLAYBOOKS = {
881
881
  lifecycle_env_exfil:
882
882
  'CRITIQUE: Lifecycle hook + exfiltration env via curl/wget a l\'installation. ' +
883
883
  'Machine compromise si deja installe. Rotation immediate de TOUS les secrets.',
884
+
885
+ self_destruct_eval:
886
+ 'CRITIQUE: Execution dynamique de code (eval/new Function/Module._compile) + auto-suppression du fichier execute (unlinkSync/renameSync sur __filename). ' +
887
+ 'Pattern anti-forensique professionnel: le malware execute son payload obfusque puis efface ses traces. Campagne csec-crypto-toolkit (avril 2026) exfiltre .env, GITHUB_TOKEN, NPM_TOKEN, AWS/SSH keys vers csec-supply-chain-attack.vercel.app, puis unlinkSync(__filename). ' +
888
+ 'Machine compromise si deja installe. Rotation immediate de TOUS les secrets (.env, tokens CI/CD, cles SSH). Verifier les .env des repertoires parents jusqu\'a 6 niveaux. Supprimer le package.',
884
889
  };
885
890
 
886
891
  function getPlaybook(threatType) {
@@ -2264,6 +2264,18 @@ const RULES = {
2264
2264
  ],
2265
2265
  mitre: 'T1496'
2266
2266
  },
2267
+ self_destruct_eval: {
2268
+ id: 'MUADDIB-AST-089',
2269
+ name: 'Self-Destructing Dynamic Execution',
2270
+ severity: 'CRITICAL',
2271
+ confidence: 'high',
2272
+ description: 'Execution dynamique de code (eval/new Function/Module._compile) combinee a la suppression ou renommage du fichier en cours d\'execution (unlinkSync/rmSync/renameSync sur __filename, module.filename, ou require.main.filename). Anti-forensics: le malware execute son payload obfusque puis efface ses traces. Aucun package legitime ne detruit son propre source apres execution de code dynamique. Campagne csec-crypto-toolkit (avril 2026): XOR(OrDeR_7077)+base64+new Function, exfiltre .env/.ssh/.npmrc vers csec-supply-chain-attack.vercel.app, puis unlinkSync(__filename).',
2273
+ references: [
2274
+ 'https://attack.mitre.org/techniques/T1070.004/',
2275
+ 'https://attack.mitre.org/techniques/T1140/'
2276
+ ],
2277
+ mitre: 'T1070.004'
2278
+ },
2267
2279
  version_99_preinstall: {
2268
2280
  id: 'MUADDIB-PKG-019',
2269
2281
  name: 'Dependency Confusion Version Indicator',
@@ -144,7 +144,12 @@ const GIT_HOOKS = [
144
144
 
145
145
  // Suspicious C2/exfiltration domains (HIGH severity)
146
146
  const SUSPICIOUS_DOMAINS_HIGH = [
147
+ // OAST (Out-of-band Application Security Testing) callback domains.
148
+ // Legitimate only for authorized pentesting — in published npm packages,
149
+ // these are always reconnaissance/exfiltration callbacks (dependency confusion, SSRF confirmation).
147
150
  'oastify.com', 'oast.fun', 'oast.me', 'oast.live',
151
+ 'oast.online', 'oast.pro',
152
+ 'interact.sh', 'projectdiscovery.io',
148
153
  'burpcollaborator.net', 'webhook.site', 'pipedream.net',
149
154
  'requestbin.com', 'hookbin.com', 'canarytokens.com',
150
155
  // GlassWorm C2 IPs (mars 2026)
@@ -167,7 +172,11 @@ const SUSPICIOUS_DOMAINS_HIGH = [
167
172
  'minhdong.site', // Facebook credential proxy (fca-mmtat)
168
173
  'ltidi.storage.googleapis.com', // KuCoin dependency confusion payload
169
174
  'jsonkeeper.com', // Robert King campaign C2 dead drop
170
- 'npoint.io' // Robert King campaign C2 dead drop
175
+ 'npoint.io', // Robert King campaign C2 dead drop
176
+ // v2.10.93: Security review 2026-04-10→17 findings
177
+ 'csec-supply-chain-attack.vercel.app', // csec-crypto-toolkit credential stealer C2 (XOR+Function+unlink)
178
+ 'files.giftedtech.co.ke', // silva-baileys V3 newsletter JID remote loader
179
+ 'phish.sh' // JET/SkipTheDishes depconf webhook exfil
171
180
  ];
172
181
 
173
182
  // Suspicious tunnel/proxy domains (MEDIUM severity)
@@ -23,6 +23,26 @@ function handlePostWalk(ctx) {
23
23
  });
24
24
  }
25
25
 
26
+ // v2.10.93: csec-style credential stealer — dynamic exec + self-deletion of __filename.
27
+ // csec-crypto-toolkit (avril 2026): XOR-obfuscated setup.js that exfiltrates
28
+ // .env/.ssh/.npmrc to csec-supply-chain-attack.vercel.app, then unlinks itself
29
+ // and replaces package.json with package.md to erase traces.
30
+ // Threat model: the combination of (a) dynamic code execution from a variable
31
+ // (eval/new Function/Module._compile) with (b) self-deletion of the currently
32
+ // executing file is a professional anti-forensics signature. Legitimate packages
33
+ // do not destroy their own source after executing obfuscated code.
34
+ // Suppressed in dist/build to avoid bundler self-cleanup FPs (rare but possible).
35
+ if (ctx.hasDynamicExec && ctx.hasSelfDelete) {
36
+ const isDistFile = /^(dist|build|out|output)[/\\]/i.test(ctx.relFile) ||
37
+ /\.(bundle|min)\.js$/i.test(ctx.relFile);
38
+ ctx.threats.push({
39
+ type: 'self_destruct_eval',
40
+ severity: isDistFile ? 'HIGH' : 'CRITICAL',
41
+ message: 'Anti-forensics: dynamic code execution (eval/new Function/Module._compile) + self-deletion of __filename — malware staging with trace removal (csec pattern).',
42
+ file: ctx.relFile
43
+ });
44
+ }
45
+
26
46
  // SANDWORM_MODE R7: env harvesting = Object.entries/keys/values(process.env) + sensitive pattern in file
27
47
  if (ctx.hasEnvEnumeration && ctx.hasEnvHarvestPattern && ctx.hasNetworkCallInFile) {
28
48
  ctx.threats.push({
@@ -140,6 +140,10 @@ function analyzeFile(content, filePath, basePath) {
140
140
  hasTempFileExec: false,
141
141
  hasFileDelete: false,
142
142
  hasDevShmInContent: /\/dev\/shm\b/.test(content),
143
+ // v2.10.93: csec-style self-deletion — unlink/rename of __filename targets the
144
+ // executing file itself. Distinct from hasFileDelete (any file). Combined with
145
+ // hasDynamicExec in a compound to flag anti-forensics obfuscated stealers.
146
+ hasSelfDelete: /\b(?:unlinkSync|unlink|rmSync|renameSync|rm)\s*\(\s*(?:__filename|module\.filename|require\.main\.filename)\b/.test(content),
143
147
  // SANDWORM_MODE P2: env harvesting co-occurrence
144
148
  hasEnvEnumeration: false, // Object.entries/keys/values(process.env)
145
149
  hasEnvHarvestPattern: /\b(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|NPM|AWS|SSH|WEBHOOK)\b/.test(content),
@@ -324,11 +324,41 @@ async function scanPackageJson(targetPath) {
324
324
  /\/\/192\.168\.\d{1,3}\.\d{1,3}[:/]/,
325
325
  /\/\/172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}[:/]/
326
326
  ].some(p => p.test(urlLower));
327
+
328
+ // v2.10.93: External raw tarball URL as dep — ltidi chain attack pattern.
329
+ // GitHub/GitLab/Bitbucket release tarballs are legitimate; everything else
330
+ // pointing to .tgz/.tar.gz/.zip is payload delivery via third-party storage
331
+ // (GCS, S3, CDNs). The stub package bypasses all lifecycle/obfuscation scanners
332
+ // because the malicious code lives in the external tarball fetched at install.
333
+ const isTarballUrl = /\.(tgz|tar\.gz|tar\.bz2|zip)(\?|#|$)/.test(urlLower);
334
+ const isLegitTarballHost = [
335
+ /\/\/github\.com\//,
336
+ /\/\/codeload\.github\.com\//,
337
+ /\/\/objects\.githubusercontent\.com\//,
338
+ /\/\/gitlab\.com\//,
339
+ /\/\/bitbucket\.org\//,
340
+ /\/\/registry\.npmjs\.org\//,
341
+ /\/\/registry\.yarnpkg\.com\//
342
+ ].some(p => p.test(urlLower));
343
+ const isExternalTarball = isTarballUrl && !isLegitTarballHost;
344
+
345
+ let severity;
346
+ let note;
347
+ if (isSuspicious) {
348
+ severity = 'CRITICAL';
349
+ note = ' (tunnel/private/localhost)';
350
+ } else if (isExternalTarball) {
351
+ severity = 'CRITICAL';
352
+ note = ' (external raw tarball on third-party host — chain attack pattern, npm registry audit bypass)';
353
+ } else {
354
+ severity = 'HIGH';
355
+ note = ' (unusual, verify source)';
356
+ }
357
+
327
358
  threats.push({
328
359
  type: 'dependency_url_suspicious',
329
- severity: isSuspicious ? 'CRITICAL' : 'HIGH',
330
- message: `Dependency "${depName}" uses HTTP URL: ${depVersion}` +
331
- (isSuspicious ? ' (tunnel/private/localhost)' : ' (unusual, verify source)'),
360
+ severity,
361
+ message: `Dependency "${depName}" uses HTTP URL: ${depVersion}${note}`,
332
362
  file: 'package.json'
333
363
  });
334
364
  }
package/src/scoring.js CHANGED
@@ -253,6 +253,7 @@ const DIST_EXEMPT_TYPES = new Set([
253
253
  'npm_publish_worm', // exec("npm publish") (worm propagation)
254
254
  'curl_env_exfil', // curl/wget env exfil in lifecycle (always malicious)
255
255
  'function_constructor_require', // new Function.constructor("require") (always malicious)
256
+ 'self_destruct_eval', // dynamic exec + unlink __filename (csec anti-forensics)
256
257
  // Dangerous shell commands in dist/ are real threats, never bundler output
257
258
  'dangerous_exec',
258
259
  // Compound scoring rules — co-occurrence signals, never FP
@@ -966,7 +967,11 @@ function calculateRiskScore(deduped, intentResult) {
966
967
  );
967
968
  const _hasHC = deduped.some(t => HIGH_CONFIDENCE_MALICE_TYPES.has(t.type));
968
969
  const _hasCompound = deduped.some(t => t.compound === true);
969
- if (!_hasLifecycle && !_hasHC && !_hasCompound) {
970
+ // v2.10.89: staged_payload + suspicious_domain(HIGH) = confirmed C2 eval, bypass MT-1 cap
971
+ // json-spacer, reactvora: eval(data.content) from jsonkeeper.com is always malicious
972
+ const _hasStagedC2 = deduped.some(t => t.type === 'staged_payload') &&
973
+ deduped.some(t => t.type === 'suspicious_domain' && t.severity === 'HIGH');
974
+ if (!_hasLifecycle && !_hasHC && !_hasCompound && !_hasStagedC2) {
970
975
  riskScore = Math.min(riskScore, 35);
971
976
  }
972
977