muaddib-scanner 2.10.85 → 2.10.86

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.85",
3
+ "version": "2.10.86",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -285,6 +285,16 @@ async function scanPackage(name, version, ecosystem, tarballUrl, registryMeta, s
285
285
  const cacheTrigger = meta._cacheTrigger || null;
286
286
 
287
287
  try {
288
+ // Pre-download size check: reject packages known to exceed MAX_TARBALL_SIZE
289
+ // from registry metadata, without wasting a download + 300s timeout.
290
+ // unpackedSize is available from getNpmLatestTarball() after lazy resolution.
291
+ const metaSize = meta.unpackedSize || 0;
292
+ if (metaSize > MAX_TARBALL_SIZE) {
293
+ console.log(`[MONITOR] SIZE_REJECT: ${name}@${version} — metadata size ${(metaSize / 1024 / 1024).toFixed(1)}MB exceeds ${(MAX_TARBALL_SIZE / 1024 / 1024).toFixed(0)}MB limit (skipped without download)`);
294
+ stats.scanned++;
295
+ return;
296
+ }
297
+
288
298
  const tgzPath = path.join(tmpDir, 'package.tar.gz');
289
299
 
290
300
  // Layer 3: Check tarball cache before downloading
@@ -729,7 +739,10 @@ async function scanPackage(name, version, ecosystem, tarballUrl, registryMeta, s
729
739
  const canary = isCanaryEnabled();
730
740
  const reason = tier === 2 ? ' (T2, queue low)' : tier === '1b' ? ' (T1b, conditional)' : '';
731
741
  console.log(`[MONITOR] SANDBOX${reason}: launching for ${name}@${version}${canary ? ' (canary: on)' : ''}...`);
732
- sandboxResult = await runSandbox(name, { canary });
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
+ const maxRuns = tier === '1a' ? undefined : 1;
745
+ sandboxResult = await runSandbox(name, { canary, maxRuns });
733
746
  console.log(`[MONITOR] SANDBOX: ${name}@${version} → score: ${sandboxResult.score}, severity: ${sandboxResult.severity}`);
734
747
 
735
748
  // Check for canary exfiltration findings and send dedicated alert
@@ -1153,11 +1166,13 @@ async function resolveTarballAndScan(item, stats, dailyAlerts, recentlyScanned,
1153
1166
  let publishResult = null;
1154
1167
  let maintainerResult = null;
1155
1168
 
1156
- if (item.ecosystem === 'npm' && !item.fastTrack) {
1169
+ const TEMPORAL_LOAD_SHED_THRESHOLD = 2000;
1170
+ const skipTemporal = item.fastTrack || scanQueue.length > TEMPORAL_LOAD_SHED_THRESHOLD;
1171
+ if (item.ecosystem === 'npm' && !skipTemporal) {
1157
1172
  // Run all 4 temporal checks in parallel — each is independent.
1158
- // With metadata cache (temporal-analysis.js), the 4 modules share 1 HTTP request.
1159
- // Skipped for fast-track packages (large boring packages temporal checks make
1160
- // 4 HTTP requests to npm registry per package, pointless for 50MB enterprise packages).
1173
+ // AST diff alone consumes 5 HTTP semaphore slots per package (2 tarball downloads + 3 metadata).
1174
+ // With 16 workers that's 80 slot requests for 10 slots workers blocked 80% of the time.
1175
+ // Load-shed when queue > 2000: temporal analysis is a luxury during catch-up.
1161
1176
  const [tempRes, astRes, pubRes, maintRes] = await Promise.allSettled([
1162
1177
  runTemporalCheck(item.name, dailyAlerts),
1163
1178
  runTemporalAstCheck(item.name, dailyAlerts),
@@ -1168,6 +1183,8 @@ async function resolveTarballAndScan(item, stats, dailyAlerts, recentlyScanned,
1168
1183
  astResult = astRes.status === 'fulfilled' ? astRes.value : null;
1169
1184
  publishResult = pubRes.status === 'fulfilled' ? pubRes.value : null;
1170
1185
  maintainerResult = maintRes.status === 'fulfilled' ? maintRes.value : null;
1186
+ } else if (skipTemporal && item.ecosystem === 'npm' && !item.fastTrack) {
1187
+ console.log(`[MONITOR] TEMPORAL LOAD-SHED: ${item.name}@${item.version} (queue=${scanQueue.length} > ${TEMPORAL_LOAD_SHED_THRESHOLD})`);
1171
1188
  }
1172
1189
 
1173
1190
  // Abort check: if timeout fired during temporal checks, skip the expensive scan
@@ -641,12 +641,15 @@ async function runSandbox(packageName, options = {}) {
641
641
  try {
642
642
  const runtimeLabel = useGvisor ? 'gvisor' : 'docker';
643
643
  const slotInfo = skipSem ? 'deferred-slot' : `${_sandboxSemaphore.active}/${SANDBOX_CONCURRENCY_MAX}`;
644
- console.log(`[SANDBOX] Analyzing "${displayName}" in isolated container (mode: ${mode}, runtime: ${runtimeLabel}${canaryEnabled ? ', canary: on' : ''}${local ? ', local' : ''}, runs: ${TIME_OFFSETS.length}, slots: ${slotInfo})...`);
644
+ // maxRuns: cap number of time-offset runs. Default: all TIME_OFFSETS (3 runs).
645
+ // T1b/T2 use maxRuns=1 to reduce 270s→90s — time bomb detection is a luxury under load.
646
+ const effectiveRuns = Math.min(options.maxRuns || TIME_OFFSETS.length, TIME_OFFSETS.length);
647
+ console.log(`[SANDBOX] Analyzing "${displayName}" in isolated container (mode: ${mode}, runtime: ${runtimeLabel}${canaryEnabled ? ', canary: on' : ''}${local ? ', local' : ''}, runs: ${effectiveRuns}, slots: ${slotInfo})...`);
645
648
 
646
649
  const allRuns = [];
647
650
  let bestResult = cleanResult;
648
651
 
649
- for (let i = 0; i < TIME_OFFSETS.length; i++) {
652
+ for (let i = 0; i < effectiveRuns; i++) {
650
653
  const { offset, label } = TIME_OFFSETS[i];
651
654
  console.log(`[SANDBOX] Run ${i + 1}/${TIME_OFFSETS.length} (${label})...`);
652
655
 
@@ -14,7 +14,7 @@
14
14
  * NOT covered: api.npmjs.org (different server), replicate.npmjs.com (CouchDB changes stream).
15
15
  */
16
16
 
17
- const REGISTRY_SEMAPHORE_MAX = 10;
17
+ const REGISTRY_SEMAPHORE_MAX = 20;
18
18
  const RATE_LIMIT_PER_SEC = 30;
19
19
 
20
20
  // --- Concurrency semaphore ---