muaddib-scanner 2.10.86 → 2.10.87

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.10.86",
3
+ "version": "2.10.87",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -175,7 +175,9 @@ async function processDeferredItem(stats) {
175
175
  let sandboxResult;
176
176
  try {
177
177
  const canary = isCanaryEnabled();
178
- sandboxResult = await runSandbox(item.name, { canary, skipSemaphore: true });
178
+ // maxRuns=1: deferred items are T1b/T2, time bomb detection (3 runs) is a luxury.
179
+ // 90s instead of 270s per item → 3× faster deferred queue drain.
180
+ sandboxResult = await runSandbox(item.name, { canary, skipSemaphore: true, maxRuns: 1 });
179
181
  console.log(`[DEFERRED] SANDBOX COMPLETE: ${key} -> score=${sandboxResult.score}, severity=${sandboxResult.severity}`);
180
182
  } catch (err) {
181
183
  console.error(`[DEFERRED] SANDBOX ERROR: ${key} — ${err.message}`);
@@ -11,7 +11,7 @@ const path = require('path');
11
11
  const os = require('os');
12
12
  const { Worker } = require('worker_threads');
13
13
  const { run } = require('../index.js');
14
- const { runSandbox, isDockerAvailable } = require('../sandbox/index.js');
14
+ const { runSandbox, isDockerAvailable, tryAcquireSandboxSlot, SANDBOX_CONCURRENCY_MAX } = require('../sandbox/index.js');
15
15
  const { sendWebhook } = require('../webhook.js');
16
16
  const { downloadToFile, extractTarGz, sanitizePackageName } = require('../shared/download.js');
17
17
  const { MAX_TARBALL_SIZE } = require('../shared/constants.js');
@@ -737,43 +737,61 @@ async function scanPackage(name, version, ecosystem, tarballUrl, registryMeta, s
737
737
  if (shouldSandbox) {
738
738
  try {
739
739
  const canary = isCanaryEnabled();
740
- const reason = tier === 2 ? ' (T2, queue low)' : tier === '1b' ? ' (T1b, conditional)' : '';
741
- console.log(`[MONITOR] SANDBOX${reason}: launching for ${name}@${version}${canary ? ' (canary: on)' : ''}...`);
742
- // T1a: 3 runs (time bomb detection via libfaketime — mandatory for high-confidence threats)
743
- // T1b/T2: 1 run (270s→90s — time bombs are rare, throughput matters more under load)
744
740
  const maxRuns = tier === '1a' ? undefined : 1;
745
- sandboxResult = await runSandbox(name, { canary, maxRuns });
746
- console.log(`[MONITOR] SANDBOX: ${name}@${version} → score: ${sandboxResult.score}, severity: ${sandboxResult.severity}`);
747
-
748
- // Check for canary exfiltration findings and send dedicated alert
749
- const canaryFindings = (sandboxResult.findings || []).filter(f => f.type === 'canary_exfiltration');
750
- if (canaryFindings.length > 0) {
751
- console.log(`[MONITOR] CANARY EXFILTRATION: ${name}@${version}${canaryFindings.length} token(s) stolen!`);
752
- // Dedup: skip if this package was already alerted with canary_exfiltration
753
- const canaryRuleId = 'canary_exfiltration';
754
- const previousRules = alertedPackageRules.get(name);
755
- const alreadyAlerted = previousRules && previousRules.has(canaryRuleId);
756
- if (alreadyAlerted) {
757
- console.log(`[MONITOR] DEDUP: ${name} canary exfiltration (already alerted today)`);
758
- } else {
759
- const url = getWebhookUrl();
760
- if (url) {
761
- const exfiltrations = canaryFindings.map(f => ({
762
- token: f.detail.match(/exfiltrate (\S+)/)?.[1] || 'UNKNOWN',
763
- foundIn: f.detail
764
- }));
765
- const payload = buildCanaryExfiltrationWebhookEmbed(name, version, exfiltrations);
766
- try {
767
- await sendWebhook(url, payload, { rawPayload: true });
768
- console.log(`[MONITOR] Canary exfiltration webhook sent for ${name}@${version}`);
769
- // Track in dedup map
770
- if (previousRules) {
771
- previousRules.add(canaryRuleId);
772
- } else {
773
- alertedPackageRules.set(name, new Set([canaryRuleId]));
741
+
742
+ if (tier === '1a') {
743
+ // T1a: mandatory sandbox — block-wait (high-confidence threats MUST get sandbox)
744
+ console.log(`[MONITOR] SANDBOX: launching for ${name}@${version}${canary ? ' (canary: on)' : ''}...`);
745
+ sandboxResult = await runSandbox(name, { canary, maxRuns });
746
+ } else if (tryAcquireSandboxSlot()) {
747
+ // T1b/T2: non-blockingslot acquired atomically, run with skipSemaphore
748
+ const reason = tier === 2 ? ' (T2, queue low)' : ' (T1b, conditional)';
749
+ console.log(`[MONITOR] SANDBOX${reason}: launching for ${name}@${version}${canary ? ' (canary: on)' : ''}...`);
750
+ sandboxResult = await runSandbox(name, { canary, maxRuns, skipSemaphore: true });
751
+ } else {
752
+ // T1b/T2: all sandbox slots busy — defer instead of blocking worker
753
+ console.log(`[MONITOR] SANDBOX DEFER (slots full): ${name}@${version} (tier=${tier}, score=${riskScore})`);
754
+ enqueueDeferred({
755
+ name, version, ecosystem, tier, riskScore, tarballUrl,
756
+ enqueuedAt: Date.now(),
757
+ staticResult: result,
758
+ npmRegistryMeta,
759
+ retries: 0
760
+ });
761
+ stats.sandboxDeferred = (stats.sandboxDeferred || 0) + 1;
762
+ }
763
+
764
+ if (sandboxResult) {
765
+ console.log(`[MONITOR] SANDBOX: ${name}@${version} score: ${sandboxResult.score}, severity: ${sandboxResult.severity}`);
766
+
767
+ // Check for canary exfiltration findings and send dedicated alert
768
+ const canaryFindings = (sandboxResult.findings || []).filter(f => f.type === 'canary_exfiltration');
769
+ if (canaryFindings.length > 0) {
770
+ console.log(`[MONITOR] CANARY EXFILTRATION: ${name}@${version} — ${canaryFindings.length} token(s) stolen!`);
771
+ const canaryRuleId = 'canary_exfiltration';
772
+ const previousRules = alertedPackageRules.get(name);
773
+ const alreadyAlerted = previousRules && previousRules.has(canaryRuleId);
774
+ if (alreadyAlerted) {
775
+ console.log(`[MONITOR] DEDUP: ${name} canary exfiltration (already alerted today)`);
776
+ } else {
777
+ const url = getWebhookUrl();
778
+ if (url) {
779
+ const exfiltrations = canaryFindings.map(f => ({
780
+ token: f.detail.match(/exfiltrate (\S+)/)?.[1] || 'UNKNOWN',
781
+ foundIn: f.detail
782
+ }));
783
+ const payload = buildCanaryExfiltrationWebhookEmbed(name, version, exfiltrations);
784
+ try {
785
+ await sendWebhook(url, payload, { rawPayload: true });
786
+ console.log(`[MONITOR] Canary exfiltration webhook sent for ${name}@${version}`);
787
+ if (previousRules) {
788
+ previousRules.add(canaryRuleId);
789
+ } else {
790
+ alertedPackageRules.set(name, new Set([canaryRuleId]));
791
+ }
792
+ } catch (webhookErr) {
793
+ console.error(`[MONITOR] Canary webhook failed for ${name}@${version}: ${webhookErr.message}`);
774
794
  }
775
- } catch (webhookErr) {
776
- console.error(`[MONITOR] Canary webhook failed for ${name}@${version}: ${webhookErr.message}`);
777
795
  }
778
796
  }
779
797
  }
@@ -40,6 +40,19 @@ function acquireSandboxSlot() {
40
40
  });
41
41
  }
42
42
 
43
+ /**
44
+ * Non-blocking slot acquisition. Returns true if slot acquired, false if all busy.
45
+ * Atomic: check + acquire in a single synchronous call — no race condition.
46
+ * Used by T1b/T2 to defer instead of blocking when slots are full.
47
+ */
48
+ function tryAcquireSandboxSlot() {
49
+ if (_sandboxSemaphore.active < SANDBOX_CONCURRENCY_MAX) {
50
+ _sandboxSemaphore.active++;
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+
43
56
  function releaseSandboxSlot() {
44
57
  if (_sandboxSemaphore.queue.length > 0) {
45
58
  const next = _sandboxSemaphore.queue.shift();
@@ -1047,4 +1060,4 @@ function displayResults(result) {
1047
1060
  }
1048
1061
  }
1049
1062
 
1050
- module.exports = { buildSandboxImage, runSandbox, runSingleSandbox, scoreFindings, generateNetworkReport, EXFIL_PATTERNS, SAFE_DOMAINS, getSeverity, displayResults, isDockerAvailable, imageExists, isGvisorAvailable, STATIC_CANARY_TOKENS, detectStaticCanaryExfiltration, analyzePreloadLog, TIME_OFFSETS, SAFE_SANDBOX_CMDS, SANDBOX_CONCURRENCY_MAX, acquireSandboxSlot, releaseSandboxSlot, resetSandboxLimiter, getSandboxSemaphore };
1063
+ module.exports = { buildSandboxImage, runSandbox, runSingleSandbox, scoreFindings, generateNetworkReport, EXFIL_PATTERNS, SAFE_DOMAINS, getSeverity, displayResults, isDockerAvailable, imageExists, isGvisorAvailable, STATIC_CANARY_TOKENS, detectStaticCanaryExfiltration, analyzePreloadLog, TIME_OFFSETS, SAFE_SANDBOX_CMDS, SANDBOX_CONCURRENCY_MAX, acquireSandboxSlot, tryAcquireSandboxSlot, releaseSandboxSlot, resetSandboxLimiter, getSandboxSemaphore };