pompelmi 0.35.0 → 0.35.2

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.
@@ -373,7 +373,7 @@ function composeScanners(...args) {
373
373
  const all = [];
374
374
  if (opts.parallel) {
375
375
  // Parallel execution — collect all results then return
376
- const results = await Promise.allSettled(entries.map(([name, scanner]) => runWithTimeout(() => toScanFn(scanner)(input, ctx), opts.timeoutMsPerScanner)));
376
+ const results = await Promise.allSettled(entries.map(([_name, scanner]) => runWithTimeout(() => toScanFn(scanner)(input, ctx), opts.timeoutMsPerScanner)));
377
377
  for (let i = 0; i < results.length; i++) {
378
378
  const result = results[i];
379
379
  if (result.status === "fulfilled" && Array.isArray(result.value)) {
@@ -427,75 +427,62 @@ function composeScanners(...args) {
427
427
  };
428
428
  }
429
429
  function createPresetScanner(preset, opts = {}) {
430
- const scanners = [];
431
- // Always include heuristics (EICAR, PHP webshells, JS obfuscation, PE hints, etc.)
432
- scanners.push(CommonHeuristicsScanner);
430
+ const baseScanners = [CommonHeuristicsScanner];
431
+ const dynamicScannerPromises = [];
433
432
  // Add decompilation scanners based on preset
434
433
  if (preset === "decompilation-basic" ||
435
434
  preset === "decompilation-deep" ||
436
435
  preset === "malware-analysis" ||
437
436
  opts.enableDecompilation) {
438
- const depth = preset === "decompilation-deep"
437
+ const depth = preset === "decompilation-deep" || preset === "malware-analysis"
439
438
  ? "deep"
440
439
  : preset === "decompilation-basic"
441
440
  ? "basic"
442
441
  : opts.decompilationDepth || "basic";
443
- if (!opts.decompilationEngine ||
444
- opts.decompilationEngine === "binaryninja-hlil" ||
445
- opts.decompilationEngine === "both") {
446
- try {
447
- // Dynamic import to avoid bundling issues - using Function to bypass TypeScript type checking
448
- const importModule = new Function("specifier", "return import(specifier)");
449
- importModule("@pompelmi/engine-binaryninja")
450
- .then((mod) => {
451
- const binjaScanner = mod.createBinaryNinjaScanner({
452
- timeout: opts.decompilationTimeout || opts.timeout || 30000,
453
- depth,
454
- pythonPath: opts.pythonPath,
455
- binaryNinjaPath: opts.binaryNinjaPath,
456
- });
457
- scanners.push(binjaScanner);
458
- })
459
- .catch(() => {
460
- // Binary Ninja engine not available - silently skip
461
- });
462
- }
463
- catch {
464
- // Engine not installed
465
- }
442
+ let importModule;
443
+ try {
444
+ // Dynamic import to avoid bundling issues - using Function to bypass TypeScript type checking
445
+ importModule = new Function("specifier", "return import(specifier)");
466
446
  }
467
- if (!opts.decompilationEngine ||
468
- opts.decompilationEngine === "ghidra-pcode" ||
469
- opts.decompilationEngine === "both") {
470
- try {
471
- // Dynamic import for Ghidra engine (when implemented) - using Function to bypass TypeScript type checking
472
- const importModule = new Function("specifier", "return import(specifier)");
473
- importModule("@pompelmi/engine-ghidra")
474
- .then((mod) => {
475
- const ghidraScanner = mod.createGhidraScanner({
476
- timeout: opts.decompilationTimeout || opts.timeout || 30000,
477
- depth,
478
- ghidraPath: opts.ghidraPath,
479
- analyzeHeadless: opts.analyzeHeadless,
480
- });
481
- scanners.push(ghidraScanner);
482
- })
483
- .catch(() => {
484
- // Ghidra engine not available - silently skip
485
- });
486
- }
487
- catch {
488
- // Engine not installed
489
- }
447
+ catch {
448
+ importModule = undefined;
449
+ }
450
+ if (importModule &&
451
+ (!opts.decompilationEngine ||
452
+ opts.decompilationEngine === "binaryninja-hlil" ||
453
+ opts.decompilationEngine === "both")) {
454
+ dynamicScannerPromises.push(importModule("@pompelmi/engine-binaryninja")
455
+ .then((mod) => mod.createBinaryNinjaScanner({
456
+ timeout: opts.decompilationTimeout || opts.timeout || 30000,
457
+ depth,
458
+ pythonPath: opts.pythonPath,
459
+ binaryNinjaPath: opts.binaryNinjaPath,
460
+ }))
461
+ .catch(() => null));
462
+ }
463
+ if (importModule &&
464
+ (!opts.decompilationEngine ||
465
+ opts.decompilationEngine === "ghidra-pcode" ||
466
+ opts.decompilationEngine === "both")) {
467
+ dynamicScannerPromises.push(importModule("@pompelmi/engine-ghidra")
468
+ .then((mod) => mod.createGhidraScanner({
469
+ timeout: opts.decompilationTimeout || opts.timeout || 30000,
470
+ depth,
471
+ ghidraPath: opts.ghidraPath,
472
+ analyzeHeadless: opts.analyzeHeadless,
473
+ }))
474
+ .catch(() => null));
490
475
  }
491
476
  }
492
- if (scanners.length === 0) {
493
- // Fallback scanner that returns no matches
494
- return async (_input, _ctx) => {
495
- return [];
496
- };
497
- }
498
- return composeScanners(...scanners);
477
+ let composedScannerPromise;
478
+ const getComposedScanner = async () => {
479
+ composedScannerPromise ?? (composedScannerPromise = Promise.all(dynamicScannerPromises).then((dynamicScanners) => composeScanners(...baseScanners, ...dynamicScanners.filter((scanner) => scanner !== null))));
480
+ return composedScannerPromise;
481
+ };
482
+ return async (input, ctx) => {
483
+ const scanner = await getComposedScanner();
484
+ return scanner(input, ctx);
485
+ };
499
486
  }
500
487
 
501
488
  /**