muaddib-scanner 2.10.21 → 2.10.23

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.
@@ -223,9 +223,14 @@ const BLOCKCHAIN_RPC_ENDPOINTS = [
223
223
  // Solana/Web3 C2 methods — used for dead drop resolver (GlassWorm)
224
224
  const SOLANA_C2_METHODS = [
225
225
  'getSignaturesForAddress', 'getAccountInfo', 'getTransaction',
226
- 'getConfirmedSignaturesForAddress2', 'getParsedTransaction'
226
+ 'getConfirmedSignaturesForAddress2', 'getParsedTransaction',
227
+ // Blue Team v8: extended Ethereum/Web3 C2 methods
228
+ 'eth_call', 'getCode', 'getLogs'
227
229
  ];
228
230
 
231
+ // Blue Team v8: Ethereum/Web3 package names for compound blockchain C2 detection
232
+ const ETHEREUM_PACKAGES = ['ethers', 'web3', '@ethersproject/providers', '@ethersproject/contracts'];
233
+
229
234
  // Solana/Web3 package names
230
235
  const SOLANA_PACKAGES = ['@solana/web3.js', 'solana-web3.js', '@solana/web3'];
231
236
 
@@ -531,6 +536,7 @@ function handleVariableDeclarator(node, ctx) {
531
536
  }
532
537
 
533
538
  // Audit v3 B3: const AF = Object.getPrototypeOf(async function(){}).constructor
539
+ // Blue Team v8: Extended to detect nested getPrototypeOf chains (2+ levels deep)
534
540
  if (node.init?.type === 'MemberExpression') {
535
541
  const initProp = node.init.computed
536
542
  ? (node.init.property?.type === 'Literal' ? String(node.init.property.value) : null)
@@ -557,6 +563,33 @@ function handleVariableDeclarator(node, ctx) {
557
563
  file: ctx.relFile
558
564
  });
559
565
  }
566
+ // Blue Team v8: Nested getPrototypeOf — Object.getPrototypeOf(Object.getPrototypeOf(...)).constructor
567
+ // Walking up prototype chain 2+ levels to reach Function constructor from any object
568
+ if (arg.type === 'CallExpression' && arg.callee?.type === 'MemberExpression' &&
569
+ arg.callee.property?.name === 'getPrototypeOf') {
570
+ ctx.evalAliases.set(node.id.name, 'Function');
571
+ ctx.hasDynamicExec = true;
572
+ ctx.threats.push({
573
+ type: 'dangerous_constructor',
574
+ severity: 'CRITICAL',
575
+ message: `Nested Object.getPrototypeOf() chain (2+ levels) + .constructor into "${node.id.name}" — deep prototype traversal to reach Function constructor.`,
576
+ file: ctx.relFile
577
+ });
578
+ }
579
+ // Blue Team v8b (A3): Object.getPrototypeOf(variable).constructor
580
+ // When a variable (possibly holding a prototype chain result) is passed to
581
+ // getPrototypeOf and .constructor is extracted — prototype chain traversal attack.
582
+ // Covers: const C = Object.getPrototypeOf(protoVar).constructor
583
+ if (arg.type === 'Identifier') {
584
+ ctx.evalAliases.set(node.id.name, 'Function');
585
+ ctx.hasDynamicExec = true;
586
+ ctx.threats.push({
587
+ type: 'prototype_chain_constructor',
588
+ severity: 'CRITICAL',
589
+ message: `Object.getPrototypeOf(${arg.name}).constructor extracted into "${node.id.name}" — prototype chain traversal to reach Function constructor.`,
590
+ file: ctx.relFile
591
+ });
592
+ }
560
593
  }
561
594
  }
562
595
  }
@@ -591,6 +624,21 @@ function handleVariableDeclarator(node, ctx) {
591
624
  ctx.stringVarValues.set(node.id.name, strVal);
592
625
  }
593
626
 
627
+ // Blue Team v8b (B7): Track path.join() results where last arg is an image/binary filename
628
+ // Enables steganographic payload detection when the variable is used with fs.readFileSync
629
+ if (!strVal && node.init?.type === 'CallExpression') {
630
+ const initCallName = getCallName(node.init);
631
+ if ((initCallName === 'join' || initCallName === 'resolve') &&
632
+ node.init.callee?.type === 'MemberExpression' &&
633
+ node.init.arguments?.length > 0) {
634
+ const lastArg = node.init.arguments[node.init.arguments.length - 1];
635
+ if (lastArg?.type === 'Literal' && typeof lastArg.value === 'string') {
636
+ // Store the last path component so image extension check works later
637
+ ctx.stringVarValues.set(node.id.name, lastArg.value);
638
+ }
639
+ }
640
+ }
641
+
594
642
  // Track variables assigned from require.cache[...] (module cache references)
595
643
  // Used to detect writes to cached module exports (require.cache poisoning)
596
644
  if (node.init?.type === 'MemberExpression' && node.init.computed) {
@@ -890,6 +938,14 @@ function handleCallExpression(node, ctx) {
890
938
  if (reqStr && SOLANA_PACKAGES.some(pkg => reqStr === pkg)) {
891
939
  ctx.hasSolanaImport = true;
892
940
  }
941
+ // Blue Team v8: track Ethereum/Web3 imports
942
+ if (reqStr && ETHEREUM_PACKAGES.some(pkg => reqStr === pkg || reqStr.startsWith(pkg))) {
943
+ ctx.hasSolanaImport = true; // reuse flag — both indicate blockchain SDK usage
944
+ }
945
+ // Blue Team v8: track require('ws') for WebSocket C2 detection
946
+ if (reqStr === 'ws' || reqStr === 'websocket') {
947
+ ctx.hasWebSocketNew = true; // ws module provides WebSocket functionality
948
+ }
893
949
  }
894
950
 
895
951
  // Detect process.mainModule.require('child_process') — module system bypass
@@ -921,6 +977,28 @@ function handleCallExpression(node, ctx) {
921
977
  }
922
978
  }
923
979
 
980
+ // B8: require('process').mainModule.require('child_process') — indirect process access
981
+ // Pattern: require('process').mainModule.require(mod)
982
+ if (node.callee.type === 'MemberExpression' &&
983
+ node.callee.property?.type === 'Identifier' && node.callee.property.name === 'require' &&
984
+ node.callee.object?.type === 'MemberExpression' &&
985
+ node.callee.object.property?.type === 'Identifier' && node.callee.object.property.name === 'mainModule' &&
986
+ node.callee.object.object?.type === 'CallExpression' &&
987
+ getCallName(node.callee.object.object) === 'require' &&
988
+ node.callee.object.object.arguments?.[0]?.type === 'Literal' &&
989
+ node.callee.object.object.arguments[0].value === 'process') {
990
+ const arg = node.arguments?.[0];
991
+ const modName = arg ? extractStringValueDeep(arg) : null;
992
+ const DANGEROUS_MODS = ['child_process', 'fs', 'net', 'dns', 'http', 'https', 'tls'];
993
+ const severity = modName && DANGEROUS_MODS.includes(modName) ? 'CRITICAL' : 'HIGH';
994
+ ctx.threats.push({
995
+ type: 'require_process_mainmodule',
996
+ severity,
997
+ message: `require('process').mainModule.require(${modName ? "'" + modName + "'" : '...'}) — indirect process access bypasses direct process.mainModule detection.`,
998
+ file: ctx.relFile
999
+ });
1000
+ }
1001
+
924
1002
  // Detect exec/execSync with dangerous shell commands (direct or via MemberExpression)
925
1003
  const execName = callName === 'exec' || callName === 'execSync' ? callName : null;
926
1004
  const memberExec = !execName && node.callee.type === 'MemberExpression' &&
@@ -1040,6 +1118,29 @@ function handleCallExpression(node, ctx) {
1040
1118
  }
1041
1119
  }
1042
1120
 
1121
+ // Blue Team v8b (C10): require('child_process').execSync/exec(variable) — inline require + variable command
1122
+ // When require is literal 'child_process' but the command argument is not resolvable (MemberExpression, Identifier),
1123
+ // this is a hidden exec with runtime-determined command (C2 pattern: cmd from network data)
1124
+ if ((execName || memberExec) && node.callee.type === 'MemberExpression' &&
1125
+ node.callee.object?.type === 'CallExpression') {
1126
+ const innerCall = node.callee.object;
1127
+ const innerName = getCallName(innerCall);
1128
+ if (innerName === 'require' && innerCall.arguments.length > 0 &&
1129
+ innerCall.arguments[0]?.type === 'Literal' && innerCall.arguments[0].value === 'child_process') {
1130
+ const cmdArg = node.arguments[0];
1131
+ if (cmdArg && cmdArg.type !== 'Literal') {
1132
+ // Non-literal command argument with inline require — opaque shell execution
1133
+ ctx.hasDynamicExec = true;
1134
+ ctx.threats.push({
1135
+ type: 'dangerous_exec',
1136
+ severity: 'HIGH',
1137
+ message: `Inline require('child_process').${execName || memberExec}(variable) — hidden import with runtime-determined command. Typical C2 or RCE payload pattern.`,
1138
+ file: ctx.relFile
1139
+ });
1140
+ }
1141
+ }
1142
+ }
1143
+
1043
1144
  // Detect sandbox/container evasion
1044
1145
  if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
1045
1146
  const fsMethod = node.callee.property.name;
@@ -2042,6 +2143,22 @@ function handleCallExpression(node, ctx) {
2042
2143
  });
2043
2144
  }
2044
2145
  }
2146
+
2147
+ // B3: (function(){}).constructor('code')() — direct prototype chain Function access
2148
+ // Also: [].constructor.constructor, ''.constructor.constructor, (0).constructor.constructor
2149
+ if (obj?.type === 'FunctionExpression' || obj?.type === 'ArrowFunctionExpression' ||
2150
+ obj?.type === 'ArrayExpression' || obj?.type === 'Literal') {
2151
+ if (!hasOnlyStringLiteralArgs(node)) {
2152
+ ctx.hasEvalInFile = true;
2153
+ ctx.hasDynamicExec = true;
2154
+ ctx.threats.push({
2155
+ type: 'function_prototype_constructor',
2156
+ severity: 'CRITICAL',
2157
+ message: `Function constructor via prototype chain: (${obj.type === 'FunctionExpression' ? 'function(){}' : obj.type === 'ArrayExpression' ? '[]' : 'literal'}).constructor(code) — bypasses Function/eval detection.`,
2158
+ file: ctx.relFile
2159
+ });
2160
+ }
2161
+ }
2045
2162
  }
2046
2163
 
2047
2164
  // SANDWORM_MODE: Track writeFileSync/writeFile to temp paths
@@ -2164,9 +2281,37 @@ function handleCallExpression(node, ctx) {
2164
2281
  file: ctx.relFile
2165
2282
  });
2166
2283
  }
2284
+ // B1: Reflect.apply(require, null, ['child_process']) — bypasses require() call detection
2285
+ if (target.type === 'Identifier' && target.name === 'require') {
2286
+ const argsArray = node.arguments[2];
2287
+ let modName = null;
2288
+ if (argsArray?.type === 'ArrayExpression' && argsArray.elements.length > 0) {
2289
+ modName = extractStringValueDeep(argsArray.elements[0]);
2290
+ }
2291
+ const severity = modName && ['child_process', 'fs', 'net', 'dns', 'http', 'https'].includes(modName)
2292
+ ? 'CRITICAL' : 'HIGH';
2293
+ ctx.threats.push({
2294
+ type: 'reflect_apply_require',
2295
+ severity,
2296
+ message: `Reflect.apply(require, null, [${modName ? "'" + modName + "'" : '...'}]) — indirect require() bypasses static call detection.`,
2297
+ file: ctx.relFile
2298
+ });
2299
+ }
2167
2300
  }
2168
2301
  }
2169
2302
 
2303
+ // B4: __defineGetter__ / __defineSetter__ — prototype pollution via legacy API
2304
+ if (node.callee.type === 'MemberExpression' &&
2305
+ node.callee.property?.type === 'Identifier' &&
2306
+ (node.callee.property.name === '__defineGetter__' || node.callee.property.name === '__defineSetter__')) {
2307
+ ctx.threats.push({
2308
+ type: 'prototype_pollution',
2309
+ severity: 'HIGH',
2310
+ message: `${node.callee.property.name}() called — legacy prototype pollution API can hijack property access on any object.`,
2311
+ file: ctx.relFile
2312
+ });
2313
+ }
2314
+
2170
2315
  // Batch 1: process.binding('spawn_sync'/'fs') / process._linkedBinding(...)
2171
2316
  if (node.callee.type === 'MemberExpression' &&
2172
2317
  node.callee.object?.type === 'Identifier' && node.callee.object.name === 'process' &&
@@ -2194,6 +2339,144 @@ function handleCallExpression(node, ctx) {
2194
2339
  }
2195
2340
  }
2196
2341
 
2342
+ // Blue Team v8: process.dlopen() — direct native module loading bypass
2343
+ if (node.callee.type === 'MemberExpression' &&
2344
+ node.callee.object?.type === 'Identifier' && node.callee.object.name === 'process' &&
2345
+ node.callee.property?.type === 'Identifier' && node.callee.property.name === 'dlopen' &&
2346
+ node.arguments.length >= 1) {
2347
+ ctx.threats.push({
2348
+ type: 'process_binding_abuse',
2349
+ severity: 'CRITICAL',
2350
+ message: 'process.dlopen() — direct native module loading bypasses require() and all module system checks.',
2351
+ file: ctx.relFile
2352
+ });
2353
+ }
2354
+
2355
+ // Blue Team v8: dgram.createSocket() / socket.send() — UDP exfiltration tracking
2356
+ if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
2357
+ const dgramMethod = node.callee.property.name;
2358
+ if (dgramMethod === 'createSocket' && ctx.hasDgramImport) {
2359
+ ctx.hasDgramSend = true; // createSocket implies intent to use UDP
2360
+ }
2361
+ if (dgramMethod === 'send' && ctx.hasDgramImport) {
2362
+ ctx.hasDgramSend = true;
2363
+ }
2364
+ }
2365
+
2366
+ // Blue Team v8: Crontab/cron write detection — fs.writeFileSync to cron paths
2367
+ if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
2368
+ const cronWriteMethod = node.callee.property.name;
2369
+ if (['writeFileSync', 'writeFile', 'appendFileSync'].includes(cronWriteMethod) && node.arguments.length >= 1) {
2370
+ const cronPathArg = node.arguments[0];
2371
+ let cronPathStr = extractStringValueDeep(cronPathArg);
2372
+ if (!cronPathStr && cronPathArg?.type === 'Identifier' && ctx.stringVarValues?.has(cronPathArg.name)) {
2373
+ cronPathStr = ctx.stringVarValues.get(cronPathArg.name);
2374
+ }
2375
+ if (cronPathStr && (/\/etc\/cron/i.test(cronPathStr) || /crontab/i.test(cronPathStr) ||
2376
+ /\/var\/spool\/cron/i.test(cronPathStr))) {
2377
+ ctx.hasCrontabWrite = true;
2378
+ ctx.threats.push({
2379
+ type: 'crontab_systemd_write',
2380
+ severity: 'CRITICAL',
2381
+ message: `${cronWriteMethod}() writes to cron path: "${cronPathStr.substring(0, 80)}" — scheduled task persistence.`,
2382
+ file: ctx.relFile
2383
+ });
2384
+ }
2385
+ }
2386
+ }
2387
+
2388
+ // Blue Team v8: .replace() chain detection — 3+ chained .replace() calls for string mutation obfuscation
2389
+ // Pattern: 'l33t'.replace(/3/g, 'e').replace(/1/g, 'i') to reconstruct dangerous strings
2390
+ if (node.callee.type === 'MemberExpression' && node.callee.property?.type === 'Identifier' &&
2391
+ node.callee.property.name === 'replace' && node.arguments.length >= 2) {
2392
+ // Walk up the chain to count .replace() depth and try to resolve
2393
+ let depth = 1;
2394
+ let baseNode = node.callee.object;
2395
+ while (baseNode?.type === 'CallExpression' &&
2396
+ baseNode.callee?.type === 'MemberExpression' &&
2397
+ baseNode.callee.property?.type === 'Identifier' &&
2398
+ baseNode.callee.property.name === 'replace' &&
2399
+ baseNode.arguments?.length >= 2) {
2400
+ depth++;
2401
+ baseNode = baseNode.callee.object;
2402
+ }
2403
+ if (depth >= 3) {
2404
+ // Try to statically resolve the chain
2405
+ let resolved = null;
2406
+ if (baseNode?.type === 'Literal' && typeof baseNode.value === 'string') {
2407
+ resolved = baseNode.value;
2408
+ } else if (baseNode?.type === 'Identifier' && ctx.stringVarValues?.has(baseNode.name)) {
2409
+ resolved = ctx.stringVarValues.get(baseNode.name);
2410
+ }
2411
+ if (resolved !== null) {
2412
+ // Apply each .replace() in order (walk the chain from inner to outer)
2413
+ const replaceCalls = [];
2414
+ let currentNode = node;
2415
+ while (currentNode?.type === 'CallExpression' &&
2416
+ currentNode.callee?.type === 'MemberExpression' &&
2417
+ currentNode.callee.property?.name === 'replace') {
2418
+ replaceCalls.unshift(currentNode.arguments);
2419
+ currentNode = currentNode.callee.object;
2420
+ }
2421
+ for (const args of replaceCalls) {
2422
+ if (args.length >= 2) {
2423
+ let pattern = null;
2424
+ let replacement = null;
2425
+ // Extract regex or string pattern
2426
+ if (args[0].type === 'Literal' && args[0].regex) {
2427
+ try { pattern = new RegExp(args[0].regex.pattern, args[0].regex.flags); } catch { /* skip */ }
2428
+ } else if (args[0].type === 'Literal' && typeof args[0].value === 'string') {
2429
+ pattern = args[0].value;
2430
+ }
2431
+ if (args[1].type === 'Literal' && typeof args[1].value === 'string') {
2432
+ replacement = args[1].value;
2433
+ }
2434
+ if (pattern !== null && replacement !== null) {
2435
+ resolved = resolved.replace(pattern, replacement);
2436
+ } else {
2437
+ resolved = null;
2438
+ break;
2439
+ }
2440
+ }
2441
+ }
2442
+ }
2443
+ const DANGEROUS_REPLACE_KEYWORDS = /\b(child_process|eval|exec|spawn|Function|http|net|dns|require|process)\b/;
2444
+ if (resolved && DANGEROUS_REPLACE_KEYWORDS.test(resolved)) {
2445
+ ctx.threats.push({
2446
+ type: 'string_mutation_obfuscation',
2447
+ severity: 'HIGH',
2448
+ message: `String mutation via ${depth} chained .replace() calls resolves to "${resolved.substring(0, 80)}" — leet-speak/substitution evasion.`,
2449
+ file: ctx.relFile
2450
+ });
2451
+ } else if (depth >= 4) {
2452
+ // 4+ replace chains without resolution is still suspicious
2453
+ ctx.threats.push({
2454
+ type: 'string_mutation_obfuscation',
2455
+ severity: 'MEDIUM',
2456
+ message: `${depth} chained .replace() calls detected — potential string mutation obfuscation (could not fully resolve).`,
2457
+ file: ctx.relFile
2458
+ });
2459
+ }
2460
+ }
2461
+ }
2462
+
2463
+ // Blue Team v8b (A1): X.apply(require, null, [...]) — indirect require via Reflect.apply
2464
+ // Detect Reflect.apply(require, ...) or anyVar.apply(require, ...) pattern
2465
+ // This is an evasion where the attacker uses reflection to invoke require dynamically
2466
+ if (node.callee?.type === 'MemberExpression' &&
2467
+ node.callee.property?.type === 'Identifier' && node.callee.property.name === 'apply' &&
2468
+ node.arguments.length >= 2) {
2469
+ const firstArg = node.arguments[0];
2470
+ if (firstArg?.type === 'Identifier' && firstArg.name === 'require') {
2471
+ ctx.threats.push({
2472
+ type: 'dynamic_require',
2473
+ severity: 'CRITICAL',
2474
+ message: '.apply(require, ...) — indirect require() invocation via Reflect.apply or Function.prototype.apply. Evasion technique to dynamically load modules.',
2475
+ file: ctx.relFile
2476
+ });
2477
+ }
2478
+ }
2479
+
2197
2480
  // Audit v3 bypass fix: process.on('uncaughtException'/'unhandledRejection', handler)
2198
2481
  // Error handler hijacking for silent credential exfiltration
2199
2482
  if (node.callee?.type === 'MemberExpression' &&
@@ -2208,6 +2491,116 @@ function handleCallExpression(node, ctx) {
2208
2491
  }
2209
2492
 
2210
2493
  // SANDWORM_MODE R8: dns.resolve detection moved to walk.ancestor() in ast.js (FIX 5)
2494
+
2495
+ // Blue Team v8b (A7): JSON.parse with reviver that checks __proto__
2496
+ // JSON.parse(str, function(key, value) { if (key === '__proto__') ... })
2497
+ // Note: getCallName returns 'parse' (property only), so check object.name === 'JSON'
2498
+ if (callName === 'parse' && node.callee?.type === 'MemberExpression' &&
2499
+ node.callee.object?.type === 'Identifier' && node.callee.object.name === 'JSON' &&
2500
+ node.arguments.length >= 2) {
2501
+ const reviver = node.arguments[1];
2502
+ if (reviver && (reviver.type === 'FunctionExpression' || reviver.type === 'ArrowFunctionExpression')) {
2503
+ // Check if reviver body contains __proto__ reference
2504
+ const reviverSrc = reviver.start !== undefined && reviver.end !== undefined
2505
+ ? ctx._sourceCode?.slice(reviver.start, reviver.end) : '';
2506
+ if (reviverSrc && /__proto__|prototype\s*[.[]/.test(reviverSrc)) {
2507
+ ctx.hasJsonReviverProto = true;
2508
+ const hasRequireInReviver = /\brequire\s*\(/.test(reviverSrc);
2509
+ const hasProtoAssign = /Object\.prototype\s*\./.test(reviverSrc) || /\.__proto__\s*=/.test(reviverSrc);
2510
+ ctx.threats.push({
2511
+ type: 'json_reviver_pollution',
2512
+ severity: (hasRequireInReviver || hasProtoAssign) ? 'CRITICAL' : 'HIGH',
2513
+ message: (hasRequireInReviver || hasProtoAssign)
2514
+ ? 'JSON.parse reviver accesses __proto__/prototype with require() or prototype assignment — prototype pollution for code injection.'
2515
+ : 'JSON.parse reviver accesses __proto__/prototype — potential prototype pollution via untrusted JSON input.',
2516
+ file: ctx.relFile
2517
+ });
2518
+ }
2519
+ }
2520
+ }
2521
+
2522
+ // Blue Team v8b (C2): vm.runInContext/runInNewContext/compileFunction with dynamic code
2523
+ // Detect when the code argument is built from Buffer.from/base64/concat, not a string literal
2524
+ if (node.callee?.type === 'MemberExpression' && node.callee.property?.type === 'Identifier') {
2525
+ const vmMethods = ['runInContext', 'runInNewContext', 'runInThisContext', 'compileFunction'];
2526
+ if (vmMethods.includes(node.callee.property.name) && node.arguments.length > 0) {
2527
+ const codeArg = node.arguments[0];
2528
+ // Dynamic code: not a string literal, could be variable, concat, Buffer.from, etc.
2529
+ if (codeArg && codeArg.type !== 'Literal') {
2530
+ const isDynamic = codeArg.type === 'Identifier' || codeArg.type === 'BinaryExpression' ||
2531
+ codeArg.type === 'CallExpression' || codeArg.type === 'TemplateLiteral';
2532
+ if (isDynamic) {
2533
+ ctx.hasVmDynamicExec = true;
2534
+ ctx.hasDynamicExec = true;
2535
+ ctx.threats.push({
2536
+ type: 'vm_dynamic_code',
2537
+ severity: 'CRITICAL',
2538
+ message: `vm.${node.callee.property.name}() with dynamically constructed code — vm sandbox escape via runtime-built code string.`,
2539
+ file: ctx.relFile
2540
+ });
2541
+ }
2542
+ }
2543
+ }
2544
+ }
2545
+
2546
+ // Blue Team v8b (C2): vm.createContext() with custom require injection + sensitive modules
2547
+ // Detects sandbox setup that provides module access to untrusted code
2548
+ if (callName === 'createContext' && node.callee?.type === 'MemberExpression' &&
2549
+ node.callee.object?.type === 'Identifier') {
2550
+ const src = ctx._sourceCode || '';
2551
+ // Check if the same file defines a custom require for the sandbox
2552
+ const hasRequireInjection = /\.require\s*=\s*(?:\(|function\b)/.test(src) ||
2553
+ /require\s*:\s*(?:\(|function\b)/.test(src);
2554
+ const hasSensitiveModules = /require\s*\(\s*['"](?:fs|http|https|net|child_process)['"]/.test(src);
2555
+ if (hasRequireInjection && hasSensitiveModules) {
2556
+ ctx.hasDynamicExec = true;
2557
+ ctx.threats.push({
2558
+ type: 'vm_dynamic_code',
2559
+ severity: 'CRITICAL',
2560
+ message: 'vm.createContext() with custom require() injection and access to sensitive modules (fs/http/net) — sandbox that enables untrusted code execution with elevated privileges.',
2561
+ file: ctx.relFile
2562
+ });
2563
+ }
2564
+ }
2565
+
2566
+ // Blue Team v8b (B7): fs.readFileSync on image/binary files (stego pattern)
2567
+ if (node.callee?.type === 'MemberExpression' && node.callee.property?.type === 'Identifier' &&
2568
+ ['readFileSync', 'readFile'].includes(node.callee.property.name)) {
2569
+ const fileArg = node.arguments?.[0];
2570
+ let filePath = '';
2571
+ if (fileArg?.type === 'Literal' && typeof fileArg.value === 'string') {
2572
+ filePath = fileArg.value;
2573
+ } else if (fileArg?.type === 'Identifier' && ctx.stringVarValues?.has(fileArg.name)) {
2574
+ filePath = ctx.stringVarValues.get(fileArg.name);
2575
+ }
2576
+ if (/\.(png|jpg|jpeg|gif|bmp|ico|svg)$/i.test(filePath)) {
2577
+ ctx.hasBinaryFileRead = true;
2578
+ }
2579
+ }
2580
+
2581
+ // Blue Team v8b (C10): execSync/exec inside .on('message') or .on('data') callback
2582
+ if (node.callee?.type === 'MemberExpression' && node.callee.property?.type === 'Identifier' &&
2583
+ node.callee.property.name === 'on' && node.arguments.length >= 2) {
2584
+ const eventArg = node.arguments[0];
2585
+ if (eventArg?.type === 'Literal' && ['message', 'data'].includes(eventArg.value)) {
2586
+ const callback = node.arguments[1];
2587
+ if (callback && (callback.type === 'FunctionExpression' || callback.type === 'ArrowFunctionExpression')) {
2588
+ const cbSrc = callback.start !== undefined && callback.end !== undefined
2589
+ ? ctx._sourceCode?.slice(callback.start, callback.end) : '';
2590
+ if (cbSrc && /\b(execSync|exec|spawn|spawnSync)\s*\(/.test(cbSrc) &&
2591
+ /\brequire\s*\(\s*['"]child_process['"]\s*\)/.test(cbSrc)) {
2592
+ ctx.hasCallbackExec = true;
2593
+ ctx.hasDynamicExec = true;
2594
+ ctx.threats.push({
2595
+ type: 'callback_exec_rce',
2596
+ severity: 'CRITICAL',
2597
+ message: `exec/spawn inside .on('${eventArg.value}') callback with require('child_process') — remote command execution from network input.`,
2598
+ file: ctx.relFile
2599
+ });
2600
+ }
2601
+ }
2602
+ }
2603
+ }
2211
2604
  }
2212
2605
 
2213
2606
  function handleImportExpression(node, ctx) {
@@ -2232,10 +2625,16 @@ function handleImportExpression(node, ctx) {
2232
2625
  ctx.hasSolanaImport = true;
2233
2626
  }
2234
2627
  } else {
2628
+ // Blue Team v8b (C6): Dynamic import with non-literal arg — if it's a variable
2629
+ // built from URL manipulation, this is remote code loading
2630
+ const isCritical = node.source.type === 'Identifier' || node.source.type === 'TemplateLiteral' ||
2631
+ (node.source.type === 'CallExpression' && node.source.callee?.property?.name === 'replace');
2235
2632
  ctx.threats.push({
2236
2633
  type: 'dynamic_import',
2237
- severity: 'HIGH',
2238
- message: 'Dynamic import() with computed argument (possible obfuscation).',
2634
+ severity: isCritical ? 'CRITICAL' : 'HIGH',
2635
+ message: isCritical
2636
+ ? 'Dynamic import() with computed URL argument — remote code loading from dynamically constructed URL.'
2637
+ : 'Dynamic import() with computed argument (possible obfuscation).',
2239
2638
  file: ctx.relFile
2240
2639
  });
2241
2640
  }
@@ -2318,19 +2717,92 @@ function handleNewExpression(node, ctx) {
2318
2717
 
2319
2718
  // Batch 2: new Worker(code, { eval: true }) — worker_threads code execution
2320
2719
  if (node.callee.type === 'Identifier' && node.callee.name === 'Worker' &&
2321
- node.arguments.length >= 2) {
2322
- const opts = node.arguments[1];
2323
- if (opts?.type === 'ObjectExpression') {
2324
- const evalProp = opts.properties?.find(p =>
2325
- p.key?.name === 'eval' && p.value?.value === true);
2326
- if (evalProp) {
2327
- ctx.hasDynamicExec = true;
2720
+ node.arguments.length >= 1) {
2721
+ ctx.hasWorkerThread = true;
2722
+ if (node.arguments.length >= 2) {
2723
+ const opts = node.arguments[1];
2724
+ if (opts?.type === 'ObjectExpression') {
2725
+ const evalProp = opts.properties?.find(p =>
2726
+ p.key?.name === 'eval' && p.value?.value === true);
2727
+ if (evalProp) {
2728
+ ctx.hasDynamicExec = true;
2729
+ ctx.threats.push({
2730
+ type: 'worker_thread_exec',
2731
+ severity: 'HIGH',
2732
+ message: 'new Worker() with eval:true — executes arbitrary code in worker thread, bypasses main thread detection.',
2733
+ file: ctx.relFile
2734
+ });
2735
+ }
2736
+ }
2737
+ }
2738
+ // Blue Team v8: new Worker('data:...') — data URL code injection into worker
2739
+ const firstArg = node.arguments[0];
2740
+ if (firstArg?.type === 'Literal' && typeof firstArg.value === 'string' &&
2741
+ firstArg.value.startsWith('data:')) {
2742
+ ctx.hasDynamicExec = true;
2743
+ ctx.threats.push({
2744
+ type: 'worker_thread_exec',
2745
+ severity: 'HIGH',
2746
+ message: 'new Worker() with data: URL — inline code injection into worker thread.',
2747
+ file: ctx.relFile
2748
+ });
2749
+ }
2750
+ }
2751
+
2752
+ // Blue Team v8: new SharedArrayBuffer() — shared memory for covert IPC
2753
+ if (node.callee.type === 'Identifier' && node.callee.name === 'SharedArrayBuffer') {
2754
+ ctx.hasSharedArrayBuffer = true;
2755
+ }
2756
+
2757
+ // Blue Team v8: new WebSocket(url) — track for C2 compound detection
2758
+ if (node.callee.type === 'Identifier' && node.callee.name === 'WebSocket' &&
2759
+ node.arguments.length >= 1) {
2760
+ ctx.hasWebSocketNew = true;
2761
+ // Check if URL points to suspicious domain
2762
+ const wsArg = node.arguments[0];
2763
+ const wsUrl = extractStringValueDeep(wsArg);
2764
+ if (wsUrl) {
2765
+ const wsLower = wsUrl.toLowerCase();
2766
+ const isSuspiciousWs = SUSPICIOUS_DOMAINS_HIGH.some(d => wsLower.includes(d)) ||
2767
+ SUSPICIOUS_DOMAINS_MEDIUM.some(d => wsLower.includes(d));
2768
+ if (isSuspiciousWs) {
2328
2769
  ctx.threats.push({
2329
- type: 'worker_thread_exec',
2770
+ type: 'websocket_c2',
2330
2771
  severity: 'HIGH',
2331
- message: 'new Worker() with eval:true executes arbitrary code in worker thread, bypasses main thread detection.',
2772
+ message: `new WebSocket() connecting to suspicious domain: "${wsUrl.substring(0, 80)}" potential C2 channel.`,
2773
+ file: ctx.relFile
2774
+ });
2775
+ }
2776
+ }
2777
+ }
2778
+
2779
+ // B2: new FinalizationRegistry(callback) — deferred execution after GC
2780
+ // Malicious pattern: callback contains require('child_process') or exec/spawn
2781
+ if (node.callee.type === 'Identifier' && node.callee.name === 'FinalizationRegistry' &&
2782
+ node.arguments.length >= 1) {
2783
+ const callback = node.arguments[0];
2784
+ if (callback) {
2785
+ // Check if callback body contains dangerous patterns
2786
+ let hasDangerousBody = false;
2787
+ const cbSource = callback.start !== undefined && callback.end !== undefined
2788
+ ? ctx._sourceCode?.slice(callback.start, callback.end) : null;
2789
+ if (cbSource && /\b(child_process|exec|execSync|spawn|spawnSync)\b/.test(cbSource)) {
2790
+ hasDangerousBody = true;
2791
+ }
2792
+ // Also flag if the callback is a variable known to be dangerous
2793
+ if (callback.type === 'Identifier' && ctx.evalAliases?.has(callback.name)) {
2794
+ hasDangerousBody = true;
2795
+ }
2796
+ if (hasDangerousBody) {
2797
+ ctx.hasDynamicExec = true;
2798
+ ctx.threats.push({
2799
+ type: 'finalization_registry_exec',
2800
+ severity: 'CRITICAL',
2801
+ message: 'new FinalizationRegistry() with dangerous callback — deferred code execution triggered by garbage collection, evades synchronous analysis.',
2332
2802
  file: ctx.relFile
2333
2803
  });
2804
+ } else {
2805
+ ctx.hasFinalizationRegistry = true;
2334
2806
  }
2335
2807
  }
2336
2808
  }
@@ -2463,6 +2935,81 @@ function handleAssignmentExpression(node, ctx) {
2463
2935
  }
2464
2936
  }
2465
2937
 
2938
+ // B6: Symbol property hiding — obj[Symbol(...)] = require('child_process')
2939
+ if (node.left?.type === 'MemberExpression' && node.left.computed &&
2940
+ node.left.property?.type === 'CallExpression' &&
2941
+ node.left.property.callee?.type === 'Identifier' && node.left.property.callee.name === 'Symbol') {
2942
+ // Check if the right side is require('child_process') or similar dangerous module
2943
+ let isDangerous = false;
2944
+ let modName = null;
2945
+ if (node.right?.type === 'CallExpression' && getCallName(node.right) === 'require' &&
2946
+ node.right.arguments?.[0]?.type === 'Literal') {
2947
+ const rawMod = node.right.arguments[0].value;
2948
+ modName = typeof rawMod === 'string' && rawMod.startsWith('node:') ? rawMod.slice(5) : rawMod;
2949
+ if (['child_process', 'fs', 'net', 'dns', 'http', 'https'].includes(modName)) {
2950
+ isDangerous = true;
2951
+ }
2952
+ }
2953
+ // Also detect: obj[Symbol('x')] = eval / Function / exec
2954
+ if (node.right?.type === 'Identifier' && ['eval', 'Function'].includes(node.right.name)) {
2955
+ isDangerous = true;
2956
+ }
2957
+ if (isDangerous) {
2958
+ ctx.threats.push({
2959
+ type: 'symbol_property_hiding',
2960
+ severity: 'HIGH',
2961
+ message: `Dangerous module/function hidden behind Symbol property — obj[Symbol(...)] = ${modName ? "require('" + modName + "')" : node.right?.name || '...'}, evades string-based property enumeration.`,
2962
+ file: ctx.relFile
2963
+ });
2964
+ }
2965
+ }
2966
+
2967
+ // B5: Module.wrap = ... or require('module').wrap = ... — module wrapper override
2968
+ if (node.left?.type === 'MemberExpression' &&
2969
+ node.left.property?.type === 'Identifier' && node.left.property.name === 'wrap') {
2970
+ const obj = node.left.object;
2971
+ // Direct: Module.wrap = ... (where Module was imported via require('module'))
2972
+ const isModuleObj = (obj?.type === 'Identifier' && ctx.moduleAliases?.has(obj.name)) ||
2973
+ (obj?.type === 'Identifier' && obj.name === 'Module');
2974
+ // Inline: require('module').wrap = ...
2975
+ const isInlineRequire = obj?.type === 'CallExpression' && getCallName(obj) === 'require' &&
2976
+ obj.arguments?.[0]?.type === 'Literal' && obj.arguments[0].value === 'module';
2977
+ if (isModuleObj || isInlineRequire) {
2978
+ ctx.threats.push({
2979
+ type: 'module_wrap_override',
2980
+ severity: 'CRITICAL',
2981
+ message: 'Module.wrap overridden — module wrapper function hijacked, allows injecting code into every loaded module.',
2982
+ file: ctx.relFile
2983
+ });
2984
+ }
2985
+ }
2986
+
2987
+ // Blue Team v8b (A4): Module._resolveFilename / _compile / _extensions hijack
2988
+ // Any assignment to these private Module APIs is a supply-chain attack vector
2989
+ if (node.left?.type === 'MemberExpression' && !node.left.computed &&
2990
+ node.left.property?.type === 'Identifier' &&
2991
+ ['_resolveFilename', '_compile', '_extensions', '_findPath', '_nodeModulePaths'].includes(node.left.property.name)) {
2992
+ // Check if the object is Module, a module alias, or a constructor chain
2993
+ const obj = node.left.object;
2994
+ const isModuleRef = (obj?.type === 'Identifier' && (ctx.moduleAliases?.has(obj.name) || obj.name === 'Module')) ||
2995
+ // require('module')._resolveFilename = ...
2996
+ (obj?.type === 'CallExpression' && getCallName(obj) === 'require' && obj.arguments?.[0]?.value === 'module') ||
2997
+ // x.constructor._resolveFilename = ... (any .constructor chain)
2998
+ (obj?.type === 'MemberExpression' && obj.property?.type === 'Identifier' && obj.property.name === 'constructor');
2999
+ // Also match: proc.mainModule.constructor._resolveFilename (deeper chain)
3000
+ const isDeepChain = obj?.type === 'MemberExpression' && obj.property?.type === 'Identifier' &&
3001
+ ['_resolveFilename', '_compile', '_extensions', '_findPath', '_nodeModulePaths'].includes(node.left.property.name);
3002
+ if (isModuleRef || isDeepChain || (obj?.type === 'MemberExpression' && obj.property?.name === 'constructor')) {
3003
+ ctx.hasModuleInternalsHijack = true;
3004
+ ctx.threats.push({
3005
+ type: 'module_internals_hijack',
3006
+ severity: 'CRITICAL',
3007
+ message: `Assignment to Module.${node.left.property.name} — module system internals hijacked. All subsequent require() calls can be intercepted.`,
3008
+ file: ctx.relFile
3009
+ });
3010
+ }
3011
+ }
3012
+
2466
3013
  // Detect object property indirection: obj.exec = require('child_process').exec
2467
3014
  // or obj.fn = eval — stashing dangerous functions in object properties
2468
3015
  if (node.left?.type === 'MemberExpression' && node.right) {
@@ -2517,6 +3064,17 @@ function handleAssignmentExpression(node, ctx) {
2517
3064
  }
2518
3065
  }
2519
3066
 
3067
+ // B4: Prototype pollution — __proto__ assignment
3068
+ if (node.left?.type === 'MemberExpression' && !node.left.computed &&
3069
+ node.left.property?.type === 'Identifier' && node.left.property.name === '__proto__') {
3070
+ ctx.threats.push({
3071
+ type: 'prototype_pollution',
3072
+ severity: 'HIGH',
3073
+ message: `__proto__ assignment on ${node.left.object?.name || 'object'} — prototype pollution can hijack inherited properties across all objects.`,
3074
+ file: ctx.relFile
3075
+ });
3076
+ }
3077
+
2520
3078
  if (node.left?.type === 'MemberExpression') {
2521
3079
  const left = node.left;
2522
3080
 
@@ -3021,6 +3579,160 @@ function handlePostWalk(ctx) {
3021
3579
  file: ctx.relFile
3022
3580
  });
3023
3581
  }
3582
+
3583
+ // B2 compound: FinalizationRegistry + exec/network in same file = deferred malicious execution
3584
+ if (ctx.hasFinalizationRegistry && ctx.hasDynamicExec) {
3585
+ ctx.threats.push({
3586
+ type: 'finalization_registry_exec',
3587
+ severity: 'CRITICAL',
3588
+ message: 'FinalizationRegistry + dynamic execution in same file — deferred code execution triggered by garbage collection.',
3589
+ file: ctx.relFile
3590
+ });
3591
+ }
3592
+
3593
+ // Blue Team v8: SharedArrayBuffer + Worker = covert shared memory IPC
3594
+ // Pattern: SharedArrayBuffer enables worker-to-main-thread communication
3595
+ // that bypasses message channel monitoring. Alone = MEDIUM, with exec = HIGH.
3596
+ if (ctx.hasSharedArrayBuffer && ctx.hasWorkerThread) {
3597
+ ctx.threats.push({
3598
+ type: 'shared_memory_ipc',
3599
+ severity: ctx.hasDynamicExec ? 'HIGH' : 'MEDIUM',
3600
+ message: ctx.hasDynamicExec
3601
+ ? 'SharedArrayBuffer + Worker + dynamic execution — covert shared memory IPC with code execution.'
3602
+ : 'SharedArrayBuffer + Worker — shared memory IPC channel bypasses standard message monitoring.',
3603
+ file: ctx.relFile
3604
+ });
3605
+ }
3606
+
3607
+ // Blue Team v8: dgram/UDP exfiltration — dgram import + send in same file
3608
+ if (ctx.hasDgramImport && ctx.hasDgramSend) {
3609
+ const hasExfilSignal = ctx.threats.some(t =>
3610
+ t.file === ctx.relFile && (t.type === 'env_access' || t.type === 'sensitive_string')
3611
+ );
3612
+ ctx.threats.push({
3613
+ type: 'udp_exfiltration',
3614
+ severity: hasExfilSignal ? 'CRITICAL' : 'HIGH',
3615
+ message: hasExfilSignal
3616
+ ? 'UDP socket (dgram) with credential/env access — data exfiltration via UDP bypasses HTTP-level monitoring.'
3617
+ : 'UDP socket (dgram) createSocket + send — non-HTTP data channel. UDP exfiltration bypasses HTTP firewalls.',
3618
+ file: ctx.relFile
3619
+ });
3620
+ }
3621
+
3622
+ // Blue Team v8: WebSocket C2 compound — WebSocket + exec/spawn in same file
3623
+ if (ctx.hasWebSocketNew && ctx.hasDynamicExec) {
3624
+ // Only emit if no websocket_c2 already emitted (from suspicious domain detection)
3625
+ if (!ctx.threats.some(t => t.type === 'websocket_c2' && t.file === ctx.relFile)) {
3626
+ ctx.threats.push({
3627
+ type: 'websocket_c2',
3628
+ severity: 'HIGH',
3629
+ message: 'WebSocket connection + dynamic execution in same file — potential WebSocket C2 channel for remote command execution.',
3630
+ file: ctx.relFile
3631
+ });
3632
+ }
3633
+ }
3634
+
3635
+ // Blue Team v8: Extended blockchain C2 — Ethereum/Web3 import + C2 methods
3636
+ // Extends GlassWorm detection to cover ethers.js/web3.js patterns
3637
+ if (!ctx.hasSolanaImport && ctx.hasSolanaC2Method) {
3638
+ // C2 method detected without Solana import — check if Ethereum packages are used
3639
+ const hasEthImport = /\brequire\s*\(\s*['"](?:ethers|web3|@ethersproject)/i.test(ctx._sourceCode || '');
3640
+ if (hasEthImport) {
3641
+ ctx.threats.push({
3642
+ type: 'blockchain_c2_resolution',
3643
+ severity: ctx.hasDynamicExec ? 'CRITICAL' : 'HIGH',
3644
+ message: 'Ethereum/Web3 import + blockchain C2 method — ' +
3645
+ (ctx.hasDynamicExec
3646
+ ? 'dead drop resolver with dynamic execution. Blockchain C2 pattern confirmed.'
3647
+ : 'potential dead drop resolver. Technique: C2 commands stored in blockchain transactions.'),
3648
+ file: ctx.relFile
3649
+ });
3650
+ }
3651
+ }
3652
+
3653
+ // Blue Team v8b (A6): with statement + Proxy + require/exec in same file = sandbox escape compound
3654
+ // Boost: if with_body_dangerous AND proxy trap detected → ensure minimum CRITICAL score
3655
+ const hasWithDangerous = ctx.threats.some(t => t.type === 'with_body_dangerous' && t.file === ctx.relFile);
3656
+ const hasProxyInFile = ctx.hasProxyTrap || ctx.proxyHandlerVars?.size > 0;
3657
+ if (hasWithDangerous && hasProxyInFile) {
3658
+ // Elevate existing with_body_dangerous to CRITICAL if not already
3659
+ for (const t of ctx.threats) {
3660
+ if (t.type === 'with_body_dangerous' && t.file === ctx.relFile && t.severity !== 'CRITICAL') {
3661
+ t.severity = 'CRITICAL';
3662
+ t.message = 'with() + Proxy compound: Proxy trap intercepts all scope resolution inside with block — complete API hijacking for sandbox escape.';
3663
+ }
3664
+ }
3665
+ }
3666
+
3667
+ // Blue Team v8b (B7): Binary file read + new Function/eval = steganographic payload
3668
+ if (ctx.hasBinaryFileRead && ctx.hasDynamicExec) {
3669
+ ctx.threats.push({
3670
+ type: 'stego_binary_exec',
3671
+ severity: 'CRITICAL',
3672
+ message: 'Binary/image file read + dynamic execution (eval/Function) — steganographic payload extraction and execution.',
3673
+ file: ctx.relFile
3674
+ });
3675
+ } else if (ctx.hasImageFileRef && ctx.hasDynamicExec && ctx.hasWriteFileSyncInContent) {
3676
+ // Fallback: image reference + eval in same file (may not directly readFile the image)
3677
+ ctx.threats.push({
3678
+ type: 'stego_binary_exec',
3679
+ severity: 'HIGH',
3680
+ message: 'Image file reference + dynamic execution + file I/O in same file — potential steganographic payload pattern.',
3681
+ file: ctx.relFile
3682
+ });
3683
+ }
3684
+
3685
+ // Blue Team v8b (C1): AsyncLocalStorage + credential file read + exec/network
3686
+ // Trigger on hasDynamicExec OR when dynamic_require is present (require('child_' + 'process') stored in context)
3687
+ const hasDynReqInFile = ctx.threats.some(t => t.type === 'dynamic_require' && t.file === ctx.relFile);
3688
+ if (ctx.hasAsyncLocalStorage && (ctx.hasDynamicExec || hasDynReqInFile)) {
3689
+ ctx.threats.push({
3690
+ type: 'asynclocal_context_exec',
3691
+ severity: 'HIGH',
3692
+ message: 'AsyncLocalStorage + dynamic execution/require — code execution hidden in async context, evades synchronous call-stack analysis.',
3693
+ file: ctx.relFile
3694
+ });
3695
+ }
3696
+
3697
+ // Blue Team v8b (C10): net.Socket + exec = WebSocket/TCP C2
3698
+ // Trigger on callbackExec (exec in .on('message') callback) OR hasDynamicExec (exec anywhere in file)
3699
+ if (ctx.hasNetSocketCreate && (ctx.hasCallbackExec || ctx.hasDynamicExec)) {
3700
+ if (!ctx.threats.some(t => t.type === 'websocket_c2' && t.file === ctx.relFile)) {
3701
+ ctx.threats.push({
3702
+ type: 'websocket_c2',
3703
+ severity: 'CRITICAL',
3704
+ message: 'net.Socket + exec in same file — persistent TCP/WebSocket C2 with remote command execution.',
3705
+ file: ctx.relFile
3706
+ });
3707
+ }
3708
+ }
3709
+
3710
+ // Blue Team v8b (B2): CI environment fingerprinting probe — 3+ CI provider env vars in same file
3711
+ // Indicates multi-provider CI detection for conditional payload activation
3712
+ if (ctx.ciProviderCount >= 3) {
3713
+ ctx.threats.push({
3714
+ type: 'ci_environment_probe',
3715
+ severity: 'HIGH',
3716
+ message: `File references ${ctx.ciProviderCount} CI provider environment variables (GITHUB_ACTIONS, GITLAB_CI, etc.) — CI environment fingerprinting for targeted execution.`,
3717
+ file: ctx.relFile
3718
+ });
3719
+ }
3720
+
3721
+ // Blue Team v8: Hardcoded contract address (40-char hex) + blockchain import = C2 address
3722
+ if ((ctx.hasSolanaImport || /\brequire\s*\(\s*['"](?:ethers|web3|@ethersproject|@solana)/i.test(ctx._sourceCode || '')) &&
3723
+ /\b0x[0-9a-fA-F]{40}\b/.test(ctx._sourceCode || '')) {
3724
+ const existingBlockchain = ctx.threats.some(t =>
3725
+ t.type === 'blockchain_c2_resolution' && t.file === ctx.relFile
3726
+ );
3727
+ if (!existingBlockchain) {
3728
+ ctx.threats.push({
3729
+ type: 'blockchain_c2_resolution',
3730
+ severity: 'MEDIUM',
3731
+ message: 'Blockchain import + hardcoded contract address (0x...) — potential smart contract C2 endpoint.',
3732
+ file: ctx.relFile
3733
+ });
3734
+ }
3735
+ }
3024
3736
  }
3025
3737
 
3026
3738
  function handleWithStatement(node, ctx) {
@@ -3049,6 +3761,26 @@ function handleWithStatement(node, ctx) {
3049
3761
  file: ctx.relFile
3050
3762
  });
3051
3763
  }
3764
+ return; // Already handled as direct with(require(...))
3765
+ }
3766
+
3767
+ // B7: with(obj) { ... require('child_process') ... } — body contains dangerous require/exec
3768
+ // The with statement itself is rare in modern code; combined with dangerous APIs in body = evasion
3769
+ if (node.body) {
3770
+ const bodySource = node.body.start !== undefined && node.body.end !== undefined
3771
+ ? ctx._sourceCode?.slice(node.body.start, node.body.end) : null;
3772
+ if (bodySource && /\b(require\s*\(\s*['"]child_process['"]\s*\)|child_process|exec\s*\(|execSync\s*\(|spawn\s*\()/.test(bodySource)) {
3773
+ // Blue Team v8: Elevate to CRITICAL when with() scope object is a known Proxy variable
3774
+ const isProxyScope = node.object?.type === 'Identifier' && ctx.proxyHandlerVars?.has(node.object.name);
3775
+ ctx.threats.push({
3776
+ type: 'with_body_dangerous',
3777
+ severity: isProxyScope ? 'CRITICAL' : 'HIGH',
3778
+ message: isProxyScope
3779
+ ? `with(Proxy) + exec/require in body — Proxy trap intercepts all name resolution, enabling complete API hijacking.`
3780
+ : 'with() statement body contains require/exec/spawn — scope injection used to obscure dangerous API calls.',
3781
+ file: ctx.relFile
3782
+ });
3783
+ }
3052
3784
  }
3053
3785
  }
3054
3786