muaddib-scanner 2.10.84 → 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
|
@@ -37,11 +37,14 @@ const TIMEOUT_RATE_MIN_SAMPLES = 20;
|
|
|
37
37
|
let _prevScanned = 0;
|
|
38
38
|
let _prevTimeouts = 0;
|
|
39
39
|
|
|
40
|
-
// Throughput plateau detection: if we scaled up but throughput didn't increase
|
|
41
|
-
// we've hit I/O saturation
|
|
42
|
-
//
|
|
40
|
+
// Throughput plateau detection: if we scaled up but throughput didn't increase
|
|
41
|
+
// over MULTIPLE consecutive windows, we've hit I/O saturation.
|
|
42
|
+
// Requires 2 consecutive flat windows to trigger — a single 30s window has too
|
|
43
|
+
// much variance from sandbox timeouts (90-270s) to be reliable.
|
|
43
44
|
let _prevThroughput = 0;
|
|
44
45
|
let _lastScaleDirection = 0; // +1 = scaled up, -1 = scaled down, 0 = stable
|
|
46
|
+
let _plateauStreak = 0; // consecutive windows where throughput didn't improve after scale-up
|
|
47
|
+
const PLATEAU_STREAK_REQUIRED = 2; // must see flat throughput N times before triggering
|
|
45
48
|
|
|
46
49
|
/**
|
|
47
50
|
* Compute new target concurrency from system signals.
|
|
@@ -85,16 +88,24 @@ function computeTarget(current, queueDepth, stats) {
|
|
|
85
88
|
return { target, reason: `high_timeout_rate (${(timeoutRate * 100).toFixed(0)}%, ${timeoutDelta}/${scannedDelta})` };
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
// Priority 3: Throughput plateau — scaled up
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// Scale back instead of continuing to add workers.
|
|
91
|
+
// Priority 3: Throughput plateau — scaled up recently but throughput flat/down.
|
|
92
|
+
// Requires PLATEAU_STREAK_REQUIRED consecutive flat windows to trigger.
|
|
93
|
+
// A single bad window (sandbox timeout finishing in wrong 30s slot) is noise, not saturation.
|
|
92
94
|
if (_lastScaleDirection > 0 && _prevThroughput > 0 && scannedDelta > 0 && scannedDelta <= _prevThroughput) {
|
|
93
|
-
|
|
95
|
+
_plateauStreak++;
|
|
96
|
+
if (_plateauStreak >= PLATEAU_STREAK_REQUIRED) {
|
|
97
|
+
const prevTp = _prevThroughput;
|
|
98
|
+
_prevThroughput = scannedDelta;
|
|
99
|
+
_lastScaleDirection = -1;
|
|
100
|
+
_plateauStreak = 0;
|
|
101
|
+
return { target: clamp(current - 2), reason: `throughput_plateau (${prevTp}→${scannedDelta} scans/30s × ${PLATEAU_STREAK_REQUIRED} windows)` };
|
|
102
|
+
}
|
|
103
|
+
// Not enough consecutive flat windows yet — keep current level, don't scale up further
|
|
94
104
|
_prevThroughput = scannedDelta;
|
|
95
|
-
|
|
96
|
-
return { target: clamp(current - 2), reason: `throughput_plateau (${prevTp}→${scannedDelta} scans/30s, more workers didn't help)` };
|
|
105
|
+
return { target: current, reason: `plateau_warning (${_plateauStreak}/${PLATEAU_STREAK_REQUIRED}, ${scannedDelta} scans/30s)` };
|
|
97
106
|
}
|
|
107
|
+
// Throughput improved or no scale-up context — reset streak
|
|
108
|
+
_plateauStreak = 0;
|
|
98
109
|
|
|
99
110
|
// Priority 4: Queue depth — scale up for backlog, down toward base when idle
|
|
100
111
|
if (queueDepth > QUEUE_BACKLOG_THRESHOLD) {
|
|
@@ -128,6 +139,7 @@ function resetDeltas() {
|
|
|
128
139
|
_prevTimeouts = 0;
|
|
129
140
|
_prevThroughput = 0;
|
|
130
141
|
_lastScaleDirection = 0;
|
|
142
|
+
_plateauStreak = 0;
|
|
131
143
|
}
|
|
132
144
|
|
|
133
145
|
module.exports = {
|
package/src/monitor/queue.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1159
|
-
//
|
|
1160
|
-
//
|
|
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
|
package/src/sandbox/index.js
CHANGED
|
@@ -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
|
-
|
|
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 <
|
|
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
|
|