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 +1 -1
- package/src/monitor/deferred-sandbox.js +3 -1
- package/src/monitor/queue.js +54 -36
- package/src/sandbox/index.js +14 -1
package/package.json
CHANGED
|
@@ -175,7 +175,9 @@ async function processDeferredItem(stats) {
|
|
|
175
175
|
let sandboxResult;
|
|
176
176
|
try {
|
|
177
177
|
const canary = isCanaryEnabled();
|
|
178
|
-
|
|
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}`);
|
package/src/monitor/queue.js
CHANGED
|
@@ -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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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-blocking — slot 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
|
}
|
package/src/sandbox/index.js
CHANGED
|
@@ -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 };
|