muaddib-scanner 2.11.144 → 2.11.145

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.11.144",
3
+ "version": "2.11.145",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "target": "node_modules",
3
- "timestamp": "2026-07-01T21:56:09.524Z",
3
+ "timestamp": "2026-07-02T07:33:23.008Z",
4
4
  "threats": [
5
5
  {
6
6
  "type": "string_mutation_obfuscation",
@@ -1148,7 +1148,7 @@ const RULES = {
1148
1148
  severity: 'CRITICAL',
1149
1149
  confidence: 'high',
1150
1150
  domain: 'malware',
1151
- description: 'Reference DISSIMULEE en charcodes a un marqueur d\'environnement d\'analyse — marqueur de sandbox MUAD\'DIB (MUADDIB_GVISOR), marqueur d\'un analyseur pair, ou nom de poison-token. Seule la forme obfusquee est matchee : le code de sandbox de MUAD\'DIB lui-meme et l\'outillage securite legitime referencent ces marqueurs en clair sans risque. Aucun package legitime n\'encode un check pour ces tripwires. Le marqueur est un honeytoken plante : garde public et stable pour que tout verificateur s\'auto-incrimine.',
1151
+ description: 'Reference dissimulee (charcode/base64/hex) OU enumeration de process.env cherchant un prefix marqueur MUADDIB (marker-agnostique), vers un marqueur d\'environnement d\'analyse — marqueur de sandbox MUAD\'DIB (MUADDIB_GVISOR), marqueur d\'un analyseur pair, ou nom de poison-token. Seules les formes obfusquees/enumerees sont matchees : le code de sandbox de MUAD\'DIB lui-meme et l\'outillage securite legitime referencent ces marqueurs en clair sans risque. Aucun package legitime n\'encode ni n\'enumere un check pour ces tripwires. Le marqueur est un honeytoken plante : garde public et stable pour que tout verificateur s\'auto-incrimine.',
1152
1152
  references: [
1153
1153
  'https://attack.mitre.org/techniques/T1497/',
1154
1154
  'https://attack.mitre.org/techniques/T1480/'
@@ -47,34 +47,95 @@ const ANALYZER_MARKERS = [
47
47
  'CHAINRADAR_'
48
48
  ];
49
49
 
50
- // Bound the charcode-array decode so a pathological file can't blow the CPU budget.
51
- const MAX_DECODE_ARRAYS = 300;
50
+ // Bound the number of decode attempts so a pathological file can't blow the CPU budget.
51
+ const MAX_DECODE = 300;
52
52
  // A char-code array literal: >=6 small integers, e.g. [77,85,65,68,68,73,66,...].
53
53
  const CHARCODE_ARRAY_RE = /\[\s*(\d{1,3}(?:\s*,\s*\d{1,3}){5,})\s*\]/g;
54
+ // A base64 blob big enough to hide a marker (>=12 chars ~ a 9+ byte string).
55
+ const BASE64_RE = /['"`]([A-Za-z0-9+/]{12,}={0,2})['"`]/g;
56
+ // Hex-encoded string: a run of \xHH escapes, or a contiguous even-length hex literal.
57
+ const HEX_ESCAPE_RE = /((?:\\x[0-9a-fA-F]{2}){8,})/g;
58
+ const HEX_LITERAL_RE = /['"`]([0-9a-fA-F]{16,})['"`]/g;
59
+
60
+ // Return the first planted analyzer marker contained in `decoded`, or null.
61
+ function markerIn(decoded) {
62
+ if (!decoded) return null;
63
+ for (const m of ANALYZER_MARKERS) {
64
+ if (decoded.indexOf(m) !== -1) return m;
65
+ }
66
+ return null;
67
+ }
54
68
 
55
69
  /**
56
- * Return the first analyzer marker that appears CHARCODE-ENCODED in `content` (i.e. hidden
57
- * inside a numeric array literal that decodes to a string containing the marker), or null.
58
- * Plaintext references are intentionally ignored (see module doc). Bounded, side-effect-free.
70
+ * Return the first analyzer marker that appears OBFUSCATED in `content` hidden inside a
71
+ * charcode array, a base64 blob, or a hex string that decodes to text containing the marker
72
+ * or null. Plaintext references are intentionally ignored (see module doc): MUAD'DIB's own
73
+ * sandbox code and legit security tooling reference these markers in the clear, but no benign
74
+ * package *encodes* a check for one. Bounded (MAX_DECODE total decodes), side-effect-free.
59
75
  */
60
76
  function detectAnalyzerHoneytoken(content) {
61
- if (!content || content.indexOf('[') === -1) return null;
62
- CHARCODE_ARRAY_RE.lastIndex = 0;
63
- let match, seen = 0;
64
- while ((match = CHARCODE_ARRAY_RE.exec(content)) !== null) {
65
- if (++seen > MAX_DECODE_ARRAYS) break;
66
- const nums = match[1].split(',').map(n => parseInt(n, 10));
67
- // Only decode arrays that are entirely printable ASCII codes (a real hidden string).
68
- if (nums.some(n => !(n >= 9 && n <= 126))) continue;
69
- let decoded;
70
- try { decoded = String.fromCharCode.apply(null, nums); } catch { continue; }
71
- for (const m of ANALYZER_MARKERS) {
72
- if (decoded.indexOf(m) !== -1) return m;
77
+ if (!content) return null;
78
+ let seen = 0;
79
+
80
+ // (1) charcode array: e.g. [77,85,65,68,...] whose codes decode to a marker string
81
+ if (content.indexOf('[') !== -1) {
82
+ CHARCODE_ARRAY_RE.lastIndex = 0;
83
+ let match;
84
+ while ((match = CHARCODE_ARRAY_RE.exec(content)) !== null) {
85
+ if (++seen > MAX_DECODE) return null;
86
+ const nums = match[1].split(',').map(n => parseInt(n, 10));
87
+ if (nums.some(n => !(n >= 9 && n <= 126))) continue; // printable ASCII only
88
+ let decoded; try { decoded = String.fromCharCode.apply(null, nums); } catch { continue; }
89
+ const m = markerIn(decoded); if (m) return m;
90
+ }
91
+ }
92
+
93
+ // (2) base64 blob: e.g. Buffer.from(b64, 'base64') where b64 decodes to a marker
94
+ BASE64_RE.lastIndex = 0;
95
+ let b;
96
+ while ((b = BASE64_RE.exec(content)) !== null) {
97
+ if (++seen > MAX_DECODE) return null;
98
+ let decoded; try { decoded = Buffer.from(b[1], 'base64').toString('latin1'); } catch { continue; }
99
+ const m = markerIn(decoded); if (m) return m;
100
+ }
101
+
102
+ // (3) hex string: '\x4d\x55...' escapes or a contiguous '4d5541...' literal
103
+ for (const re of [HEX_ESCAPE_RE, HEX_LITERAL_RE]) {
104
+ re.lastIndex = 0;
105
+ let h;
106
+ while ((h = re.exec(content)) !== null) {
107
+ if (++seen > MAX_DECODE) return null;
108
+ const pairs = h[1].match(/[0-9a-fA-F]{2}/g);
109
+ if (!pairs) continue;
110
+ let decoded; try { decoded = pairs.map(x => String.fromCharCode(parseInt(x, 16))).join(''); } catch { continue; }
111
+ const m = markerIn(decoded); if (m) return m;
73
112
  }
74
113
  }
114
+
75
115
  return null;
76
116
  }
77
117
 
118
+ // process.env KEY-ENUMERATION coupled with a test against an analysis-marker prefix
119
+ // (enumerate the env keys, then test each against the distinctive MUADDIB prefix — our own
120
+ // tool name, FP-safe; bare 'GVISOR' is a real runtime and is deliberately NOT matched).
121
+ // Marker-AGNOSTIC re: the exact var name (matches any MUADDIB_* var), unlike the
122
+ // exact-name form `process.env.MUADDIB_GVISOR`. FP-safe by coupling: a scanned package has no
123
+ // legitimate reason to enumerate the environment looking for OUR sandbox-marker prefix
124
+ // (verified: MUAD'DIB reads its own MUADDIB_* vars directly, never via enumerate+startsWith).
125
+ const ENV_ENUM_RE = /Object\s*\.\s*(?:keys|entries|getOwnPropertyNames)\s*\(\s*process\s*\.\s*env|for\b[^;{}]*\bin\b\s*process\s*\.\s*env/;
126
+ const ENV_KEY_MARKER_TEST_RE = /\.\s*(?:startsWith|includes|indexOf|search|match)\s*\(\s*['"`][^'"`]*MUADDIB|['"`][^'"`]*MUADDIB[^'"`]*['"`]\s*(?:===?|!==?)|(?:===?|!==?)\s*['"`][^'"`]*MUADDIB/;
127
+
128
+ /**
129
+ * Detect generic `process.env` enumeration whose keys are tested for the distinctive MUADDIB
130
+ * prefix (case-sensitive; bare 'GVISOR' is a real runtime, deliberately not matched). Returns the matched marker family (string) or null. Marker-agnostic, FP-safe
131
+ * by coupling (env enumeration + our own tripwire prefix).
132
+ */
133
+ function detectEnvMarkerEnumeration(content) {
134
+ if (!content || content.indexOf('process') === -1) return null;
135
+ if (!ENV_ENUM_RE.test(content)) return null;
136
+ return ENV_KEY_MARKER_TEST_RE.test(content) ? 'MUADDIB' : null;
137
+ }
138
+
78
139
  /** Count silent `process.exit(` guards — the magnitude of the "detonation-gate wall". */
79
140
  function countExitGuards(content) {
80
141
  if (!content) return 0;
@@ -87,4 +148,4 @@ function hasHostRecon(content) {
87
148
  return !!content && /\.\s*(?:hostname|userInfo|networkInterfaces)\s*\(/.test(content);
88
149
  }
89
150
 
90
- module.exports = { ANALYZER_MARKERS, detectAnalyzerHoneytoken, countExitGuards, hasHostRecon };
151
+ module.exports = { ANALYZER_MARKERS, detectAnalyzerHoneytoken, detectEnvMarkerEnumeration, countExitGuards, hasHostRecon };
@@ -5,8 +5,8 @@ const { networkDestinationsAllBenign } = require('../../sdk-destination.js');
5
5
  function handlePostWalk(ctx) {
6
6
  // ── Anti-analysis / sandbox evasion (2026, @longzy DPRK "Contagious Interview") ──
7
7
  // We detect the EVASION rather than trying to hide the sandbox (an unwinnable arms race).
8
- // (1) analyzer_honeytoken_reference (CRITICAL, FP~0): the file hides — behind charcode
9
- // encoding — a check for a known analysis-environment tripwire (our own MUADDIB_GVISOR,
8
+ // (1) analyzer_honeytoken_reference (CRITICAL, FP~0): the file hides — behind charcode/base64/hex
9
+ // encoding, OR via process.env key-enumeration — a check for a known tripwire (our own MUADDIB_GVISOR,
10
10
  // a peer analyzer marker, a poison-token name). Only the OBFUSCATED form is matched, so
11
11
  // MUAD'DIB's own sandbox code (plaintext) and legit security tooling never trip it. The
12
12
  // marker is a planted honeytoken: kept public+stable so any checker self-incriminates.
@@ -19,7 +19,14 @@ function handlePostWalk(ctx) {
19
19
  ctx.threats.push({
20
20
  type: 'analyzer_honeytoken_reference',
21
21
  severity: 'CRITICAL',
22
- message: `Charcode-hidden reference to an analysis-environment marker ("${ctx.analyzerHoneytokenHit}") — sandbox/analyzer evasion. No legitimate package obfuscates a check for this tripwire.`,
22
+ message: `Obfuscated (charcode/base64/hex-encoded) reference to an analysis-environment marker ("${ctx.analyzerHoneytokenHit}") — sandbox/analyzer evasion. No legitimate package encodes a check for this tripwire.`,
23
+ file: ctx.relFile
24
+ });
25
+ } else if (ctx.envMarkerEnumHit) {
26
+ ctx.threats.push({
27
+ type: 'analyzer_honeytoken_reference',
28
+ severity: 'CRITICAL',
29
+ message: `Enumerates process.env keys and tests them against an analyzer-marker prefix (${ctx.envMarkerEnumHit}) — marker-agnostic sandbox/analyzer evasion (matches any MUADDIB_* var, not a fixed name). No legitimate package scans the environment for this tripwire.`,
23
30
  file: ctx.relFile
24
31
  });
25
32
  } else if (
@@ -13,7 +13,7 @@ const {
13
13
  handleWithStatement,
14
14
  handlePostWalk
15
15
  } = require('./ast-detectors');
16
- const { detectAnalyzerHoneytoken, countExitGuards, hasHostRecon } = require('./ast-detectors/anti-evasion.js');
16
+ const { detectAnalyzerHoneytoken, detectEnvMarkerEnumeration, countExitGuards, hasHostRecon } = require('./ast-detectors/anti-evasion.js');
17
17
 
18
18
  // Check if credential keywords appear INSIDE regex literals or new RegExp() patterns.
19
19
  // Only true when the keyword is part of the regex pattern itself, not just a string elsewhere in the file.
@@ -166,6 +166,7 @@ function analyzeFile(content, filePath, basePath) {
166
166
  hasHostRecon: hasHostRecon(content),
167
167
  hasProcessEnvRead: /process\s*\.\s*env\b/.test(content),
168
168
  analyzerHoneytokenHit: detectAnalyzerHoneytoken(content),
169
+ envMarkerEnumHit: detectEnvMarkerEnumeration(content),
169
170
  hasJsReverseShell: /\bnet\.Socket\b/.test(content) &&
170
171
  /\.connect\s*\(/.test(content) &&
171
172
  /\.pipe\b/.test(content) &&