muaddib-scanner 2.11.47 → 2.11.48
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
package/src/ioc/bootstrap.js
CHANGED
|
@@ -13,6 +13,16 @@ const IOCS_URL = 'https://github.com/DNSZLSK/muad-dib/releases/latest/download/i
|
|
|
13
13
|
const HOME_DATA_DIR = path.join(os.homedir(), '.muaddib', 'data');
|
|
14
14
|
const IOCS_PATH = path.join(HOME_DATA_DIR, 'iocs.json');
|
|
15
15
|
|
|
16
|
+
// Local bundled IOC file (committed in repo) — when present we don't need a network download.
|
|
17
|
+
// Loader (src/ioc/updater.js) reads this directly, so the home-cache download becomes a
|
|
18
|
+
// no-op for users who already have the source tree.
|
|
19
|
+
const LOCAL_BUNDLED_IOCS = path.join(__dirname, 'data', 'iocs.json');
|
|
20
|
+
|
|
21
|
+
// Per-process memoization: once download has failed (or been skipped), don't retry on the
|
|
22
|
+
// next scan within the same process. Eval runs hundreds of scans — without this we burn a
|
|
23
|
+
// 60s timeout per scan when the asset is missing.
|
|
24
|
+
let _ensureIocsResult = null;
|
|
25
|
+
|
|
16
26
|
// Minimum file size to consider IOCs valid (1MB)
|
|
17
27
|
const MIN_IOCS_SIZE = 1_000_000;
|
|
18
28
|
|
|
@@ -135,6 +145,9 @@ function downloadAndDecompress(url, destPath) {
|
|
|
135
145
|
* @returns {Promise<boolean>} true if IOCs are available (cached or downloaded), false if download failed
|
|
136
146
|
*/
|
|
137
147
|
async function ensureIOCs() {
|
|
148
|
+
// Per-process memoization — first scan decides, subsequent scans reuse the result.
|
|
149
|
+
if (_ensureIocsResult !== null) return _ensureIocsResult;
|
|
150
|
+
|
|
138
151
|
try {
|
|
139
152
|
// Create data directory if needed
|
|
140
153
|
if (!fs.existsSync(HOME_DATA_DIR)) {
|
|
@@ -145,10 +158,21 @@ async function ensureIOCs() {
|
|
|
145
158
|
if (fs.existsSync(IOCS_PATH)) {
|
|
146
159
|
const stat = fs.statSync(IOCS_PATH);
|
|
147
160
|
if (stat.size >= MIN_IOCS_SIZE) {
|
|
148
|
-
return true;
|
|
161
|
+
return (_ensureIocsResult = true);
|
|
149
162
|
}
|
|
150
163
|
}
|
|
151
164
|
|
|
165
|
+
// Bundled-source fast path: dev installs and the npm tarball both ship src/ioc/data/iocs.json.
|
|
166
|
+
// When that file is present, the updater loader already merges it — no need to hit GitHub.
|
|
167
|
+
if (fs.existsSync(LOCAL_BUNDLED_IOCS)) {
|
|
168
|
+
try {
|
|
169
|
+
const stat = fs.statSync(LOCAL_BUNDLED_IOCS);
|
|
170
|
+
if (stat.size >= MIN_IOCS_SIZE) {
|
|
171
|
+
return (_ensureIocsResult = true);
|
|
172
|
+
}
|
|
173
|
+
} catch { /* fall through to download */ }
|
|
174
|
+
}
|
|
175
|
+
|
|
152
176
|
// Offline / CI escape hatch: cache is empty/missing AND we don't want to
|
|
153
177
|
// hit the network. Tests and air-gapped environments use this to avoid
|
|
154
178
|
// 1-2s timeouts × N tests when the asset is unavailable. Same env var as
|
|
@@ -157,7 +181,7 @@ async function ensureIOCs() {
|
|
|
157
181
|
// the cache check so a healthy cache still returns true even in offline
|
|
158
182
|
// mode (otherwise tests that pre-populate the cache would falsely fail).
|
|
159
183
|
if (process.env.MUADDIB_NO_REGISTRY_FETCH === '1') {
|
|
160
|
-
return false;
|
|
184
|
+
return (_ensureIocsResult = false);
|
|
161
185
|
}
|
|
162
186
|
|
|
163
187
|
// Download IOCs (messages go to stderr to avoid contaminating JSON/SARIF stdout)
|
|
@@ -169,18 +193,23 @@ async function ensureIOCs() {
|
|
|
169
193
|
if (stat.size < MIN_IOCS_SIZE) {
|
|
170
194
|
try { fs.unlinkSync(IOCS_PATH); } catch {}
|
|
171
195
|
process.stderr.write('[WARN] Downloaded IOC file is too small, using compact IOCs\n');
|
|
172
|
-
return false;
|
|
196
|
+
return (_ensureIocsResult = false);
|
|
173
197
|
}
|
|
174
198
|
|
|
175
199
|
process.stderr.write('[MUADDIB] IOC database ready (' + Math.round(stat.size / 1024 / 1024) + ' MB)\n');
|
|
176
|
-
return true;
|
|
200
|
+
return (_ensureIocsResult = true);
|
|
177
201
|
} catch (err) {
|
|
178
202
|
process.stderr.write('[WARN] Could not download IOC database: ' + err.message + '\n');
|
|
179
|
-
process.stderr.write('[WARN] Continuing with YAML IOCs
|
|
180
|
-
return false;
|
|
203
|
+
process.stderr.write('[WARN] Continuing with bundled/YAML IOCs (run "muaddib update" for full coverage)\n');
|
|
204
|
+
return (_ensureIocsResult = false);
|
|
181
205
|
}
|
|
182
206
|
}
|
|
183
207
|
|
|
208
|
+
// Test hook — lets the test suite reset the memoization without spawning a fresh process.
|
|
209
|
+
function _resetEnsureIocsForTests() {
|
|
210
|
+
_ensureIocsResult = null;
|
|
211
|
+
}
|
|
212
|
+
|
|
184
213
|
module.exports = {
|
|
185
214
|
ensureIOCs,
|
|
186
215
|
downloadAndDecompress,
|
|
@@ -188,5 +217,6 @@ module.exports = {
|
|
|
188
217
|
IOCS_URL,
|
|
189
218
|
IOCS_PATH,
|
|
190
219
|
HOME_DATA_DIR,
|
|
191
|
-
MIN_IOCS_SIZE
|
|
220
|
+
MIN_IOCS_SIZE,
|
|
221
|
+
_resetEnsureIocsForTests
|
|
192
222
|
};
|
package/src/pipeline/executor.js
CHANGED
|
@@ -125,7 +125,15 @@ async function execute(targetPath, options, pythonDeps, warnings) {
|
|
|
125
125
|
const deobfuscateFn = options.noDeobfuscate ? null : deobfuscate;
|
|
126
126
|
|
|
127
127
|
// Helper: yield to event loop so spinner can animate between sync operations
|
|
128
|
-
|
|
128
|
+
// Yield to the event loop before running `fn`. Without the try/catch the
|
|
129
|
+
// exception escapes the setImmediate callback as an uncaught exception
|
|
130
|
+
// (Node's setImmediate handler is outside any await/promise frame) and
|
|
131
|
+
// crashes the process — which is what was killing evaluate on benigns that
|
|
132
|
+
// hit a corner-case in detect-cross-file.js. Now sync throws become
|
|
133
|
+
// promise rejections, picked up by the surrounding try/catch.
|
|
134
|
+
const yieldThen = (fn) => new Promise((resolve, reject) =>
|
|
135
|
+
setImmediate(() => { try { resolve(fn()); } catch (e) { reject(e); } })
|
|
136
|
+
);
|
|
129
137
|
|
|
130
138
|
// Cross-file module graph analysis (before individual scanners)
|
|
131
139
|
// Bounded: 5s timeout to prevent DoS on large/adversarial packages
|
|
@@ -395,7 +395,7 @@ function collectImportTaint(ast, currentFile, graph, taintedExports, packagePath
|
|
|
395
395
|
// const data = reader.getData() or const data = reader.data
|
|
396
396
|
if (decl.init.type === 'MemberExpression' && decl.init.object.type === 'Identifier') {
|
|
397
397
|
const modRef = localTaint['__module__' + decl.init.object.name];
|
|
398
|
-
if (modRef) {
|
|
398
|
+
if (modRef && modRef.modTaint) {
|
|
399
399
|
const propName = decl.init.property.name || decl.init.property.value;
|
|
400
400
|
if (modRef.modTaint[propName] && modRef.modTaint[propName].tainted) {
|
|
401
401
|
const t = modRef.modTaint[propName];
|
|
@@ -411,7 +411,7 @@ function collectImportTaint(ast, currentFile, graph, taintedExports, packagePath
|
|
|
411
411
|
const callee = decl.init.callee;
|
|
412
412
|
if (callee.object.type === 'Identifier') {
|
|
413
413
|
const modRef = localTaint['__module__' + callee.object.name];
|
|
414
|
-
if (modRef) {
|
|
414
|
+
if (modRef && modRef.modTaint) {
|
|
415
415
|
const propName = callee.property.name || callee.property.value;
|
|
416
416
|
if (modRef.modTaint[propName] && modRef.modTaint[propName].tainted) {
|
|
417
417
|
const t = modRef.modTaint[propName];
|
|
@@ -474,7 +474,7 @@ function collectImportTaint(ast, currentFile, graph, taintedExports, packagePath
|
|
|
474
474
|
const thisProp = decl.init.callee.object.property.name || decl.init.callee.object.property.value;
|
|
475
475
|
const methodName = decl.init.callee.property.name || decl.init.callee.property.value;
|
|
476
476
|
const modRef = thisRefs[thisProp];
|
|
477
|
-
if (modRef && methodName && modRef.modTaint[methodName] && modRef.modTaint[methodName].tainted) {
|
|
477
|
+
if (modRef && methodName && modRef.modTaint && modRef.modTaint[methodName] && modRef.modTaint[methodName].tainted) {
|
|
478
478
|
const t = modRef.modTaint[methodName];
|
|
479
479
|
localTaint[decl.id.name] = {
|
|
480
480
|
source: t.source,
|