muaddib-scanner 2.11.152 → 2.11.153
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
package/src/sandbox/index.js
CHANGED
|
@@ -18,6 +18,28 @@ const { analyzePreloadLog } = require('./analyzer.js');
|
|
|
18
18
|
const { classifyDomain } = require('./network-allowlist.js');
|
|
19
19
|
const { parseGvisorLogs, cleanupGvisorLogs } = require('./gvisor-parser.js');
|
|
20
20
|
|
|
21
|
+
// ── Event-loop stall attribution for the sandbox's SYNCHRONOUS docker CLI calls ──
|
|
22
|
+
// (2026-07-03) These execFileSync/execSync 'docker' calls run on the MAIN thread.
|
|
23
|
+
// When dockerd/runsc is slow or a gVisor container is wedged, they can block the
|
|
24
|
+
// daemon's memory-breaker loop (daemon.js) — the suspected source of the un-attributed
|
|
25
|
+
// (`op:null`) multi-minute loop-stalls that precede the cgroup OOM kills. Breadcrumb
|
|
26
|
+
// each one with the SAME beginOp/endOp mechanism as the extraction sites (queue.js) so
|
|
27
|
+
// the next stall NAMES the docker op instead of guessing. Pure observability: no change
|
|
28
|
+
// to args, timeouts, return values, or error paths. The require is GUARDED so the shared
|
|
29
|
+
// sandbox module stays usable from the CLI one-shot (and any context without the monitor);
|
|
30
|
+
// beginOp/endOp are cheap module-var writes that never throw and no-op when the lag
|
|
31
|
+
// sampler isn't running.
|
|
32
|
+
let _beginOp = () => null;
|
|
33
|
+
let _endOp = () => {};
|
|
34
|
+
try {
|
|
35
|
+
({ beginOp: _beginOp, endOp: _endOp } = require('../monitor/event-loop-monitor.js'));
|
|
36
|
+
} catch { /* monitor module unavailable (e.g. CLI) — breadcrumbs stay no-op */ }
|
|
37
|
+
|
|
38
|
+
function dockerSync(label, meta, fn) {
|
|
39
|
+
const _crumb = _beginOp(`docker:${label}`, meta || null);
|
|
40
|
+
try { return fn(); } finally { _endOp(_crumb); }
|
|
41
|
+
}
|
|
42
|
+
|
|
21
43
|
const DOCKER_IMAGE = 'muaddib-sandbox';
|
|
22
44
|
const CONTAINER_TIMEOUT = 120000; // 120 seconds
|
|
23
45
|
const SINGLE_RUN_TIMEOUT = 90000; // 90 seconds per run in multi-run mode (gVisor ~30% I/O overhead)
|
|
@@ -88,7 +110,7 @@ const _liveContainers = new Set();
|
|
|
88
110
|
function killAllSandboxContainers() {
|
|
89
111
|
const names = Array.from(_liveContainers);
|
|
90
112
|
for (const name of names) {
|
|
91
|
-
try { execFileSync('docker', ['rm', '-f', name], { stdio: 'pipe', timeout: 5000 }); } catch { /* already gone */ }
|
|
113
|
+
try { dockerSync('rm', { container: name }, () => execFileSync('docker', ['rm', '-f', name], { stdio: 'pipe', timeout: 5000 })); } catch { /* already gone */ }
|
|
92
114
|
_liveContainers.delete(name);
|
|
93
115
|
}
|
|
94
116
|
if (names.length > 0) {
|
|
@@ -177,7 +199,7 @@ const EXFIL_PATTERNS = [
|
|
|
177
199
|
|
|
178
200
|
function isDockerAvailable() {
|
|
179
201
|
try {
|
|
180
|
-
execSync('docker info', { stdio: 'pipe', timeout: 10000 });
|
|
202
|
+
dockerSync('info', null, () => execSync('docker info', { stdio: 'pipe', timeout: 10000 }));
|
|
181
203
|
return true;
|
|
182
204
|
} catch {
|
|
183
205
|
return false;
|
|
@@ -186,7 +208,7 @@ function isDockerAvailable() {
|
|
|
186
208
|
|
|
187
209
|
function imageExists() {
|
|
188
210
|
try {
|
|
189
|
-
execFileSync('docker', ['image', 'inspect', DOCKER_IMAGE], { stdio: 'pipe', timeout: 10000 });
|
|
211
|
+
dockerSync('image-inspect', null, () => execFileSync('docker', ['image', 'inspect', DOCKER_IMAGE], { stdio: 'pipe', timeout: 10000 }));
|
|
190
212
|
return true;
|
|
191
213
|
} catch {
|
|
192
214
|
return false;
|
|
@@ -201,7 +223,7 @@ let _sandboxRuntimeWarned = false; // throttle the runtime warning/refusal log
|
|
|
201
223
|
function isGvisorAvailable() {
|
|
202
224
|
if (_gvisorAvailableCache !== undefined) return _gvisorAvailableCache;
|
|
203
225
|
try {
|
|
204
|
-
const info = execSync('docker info', { encoding: 'utf8', stdio: 'pipe', timeout: 10000 });
|
|
226
|
+
const info = dockerSync('info', null, () => execSync('docker info', { encoding: 'utf8', stdio: 'pipe', timeout: 10000 }));
|
|
205
227
|
_gvisorAvailableCache = /\brunsc\b/.test(info);
|
|
206
228
|
} catch {
|
|
207
229
|
_gvisorAvailableCache = false;
|
|
@@ -430,8 +452,8 @@ async function runSingleSandbox(packageName, options = {}) {
|
|
|
430
452
|
// `docker kill` alone leaves the container record, and under gVisor the runsc
|
|
431
453
|
// sandbox can survive it — rm -f reaps both. --rm makes this a no-op on clean exit.
|
|
432
454
|
const killContainer = () => {
|
|
433
|
-
try { execFileSync('docker', ['kill', containerName], { stdio: 'pipe', timeout: 5000 }); } catch { /* may be gone */ }
|
|
434
|
-
try { execFileSync('docker', ['rm', '-f', containerName], { stdio: 'pipe', timeout: 5000 }); } catch { /* already removed */ }
|
|
455
|
+
try { dockerSync('kill', { container: containerName }, () => execFileSync('docker', ['kill', containerName], { stdio: 'pipe', timeout: 5000 })); } catch { /* may be gone */ }
|
|
456
|
+
try { dockerSync('rm', { container: containerName }, () => execFileSync('docker', ['rm', '-f', containerName], { stdio: 'pipe', timeout: 5000 })); } catch { /* already removed */ }
|
|
435
457
|
};
|
|
436
458
|
|
|
437
459
|
// Kill + arm a watchdog that force-resolves INCONCLUSIVE if the docker client
|
|
@@ -485,9 +507,9 @@ async function runSingleSandbox(packageName, options = {}) {
|
|
|
485
507
|
// Capture container ID for gVisor log retrieval (once, while container is running)
|
|
486
508
|
if (gvisorMode && !gvisorContainerId) {
|
|
487
509
|
try {
|
|
488
|
-
gvisorContainerId = execFileSync('docker', ['inspect', '--format={{.Id}}', containerName], {
|
|
510
|
+
gvisorContainerId = dockerSync('inspect', { container: containerName }, () => execFileSync('docker', ['inspect', '--format={{.Id}}', containerName], {
|
|
489
511
|
encoding: 'utf8', stdio: 'pipe', timeout: 5000
|
|
490
|
-
}).trim();
|
|
512
|
+
})).trim();
|
|
491
513
|
} catch { /* container not yet ready, will retry on next data event */ }
|
|
492
514
|
}
|
|
493
515
|
|