@isol8/core 0.14.7 → 0.16.0
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/dist/client/remote.d.ts +14 -1
- package/dist/client/remote.d.ts.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/engine/docker.d.ts +3 -5
- package/dist/engine/docker.d.ts.map +1 -1
- package/dist/engine/managers/execution-manager.d.ts +23 -0
- package/dist/engine/managers/execution-manager.d.ts.map +1 -0
- package/dist/engine/managers/index.d.ts +7 -0
- package/dist/engine/managers/index.d.ts.map +1 -0
- package/dist/engine/managers/network-manager.d.ts +15 -0
- package/dist/engine/managers/network-manager.d.ts.map +1 -0
- package/dist/engine/managers/volume-manager.d.ts +17 -0
- package/dist/engine/managers/volume-manager.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +790 -639
- package/dist/index.js.map +10 -7
- package/dist/types.d.ts +43 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/schema/isol8.config.schema.json +26 -0
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -571,6 +575,7 @@ class RemoteIsol8 {
|
|
|
571
575
|
apiKey;
|
|
572
576
|
sessionId;
|
|
573
577
|
isol8Options;
|
|
578
|
+
wsAvailable = null;
|
|
574
579
|
constructor(options, isol8Options) {
|
|
575
580
|
this.host = options.host.replace(/\/$/, "");
|
|
576
581
|
this.apiKey = options.apiKey;
|
|
@@ -604,6 +609,97 @@ class RemoteIsol8 {
|
|
|
604
609
|
return res.json();
|
|
605
610
|
}
|
|
606
611
|
async* executeStream(req) {
|
|
612
|
+
if (this.wsAvailable !== false) {
|
|
613
|
+
try {
|
|
614
|
+
yield* this.executeStreamWs(req);
|
|
615
|
+
return;
|
|
616
|
+
} catch (err) {
|
|
617
|
+
if (this.wsAvailable === null) {
|
|
618
|
+
this.wsAvailable = false;
|
|
619
|
+
} else {
|
|
620
|
+
throw err;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
yield* this.executeStreamSse(req);
|
|
625
|
+
}
|
|
626
|
+
async* executeStreamWs(req) {
|
|
627
|
+
const wsUrl = `${this.host.replace(/^http/, "ws")}/execute/ws`;
|
|
628
|
+
const events = [];
|
|
629
|
+
let resolve = null;
|
|
630
|
+
let done = false;
|
|
631
|
+
let wsError = null;
|
|
632
|
+
const ws = new WebSocket(wsUrl, {
|
|
633
|
+
headers: {
|
|
634
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
const waitForEvent = () => new Promise((r) => {
|
|
638
|
+
if (events.length > 0 || done) {
|
|
639
|
+
r();
|
|
640
|
+
} else {
|
|
641
|
+
resolve = r;
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
ws.onopen = () => {
|
|
645
|
+
this.wsAvailable = true;
|
|
646
|
+
const msg = {
|
|
647
|
+
type: "execute",
|
|
648
|
+
request: req,
|
|
649
|
+
...this.isol8Options ? { options: this.isol8Options } : {}
|
|
650
|
+
};
|
|
651
|
+
ws.send(JSON.stringify(msg));
|
|
652
|
+
};
|
|
653
|
+
ws.onmessage = (evt) => {
|
|
654
|
+
try {
|
|
655
|
+
const event = JSON.parse(typeof evt.data === "string" ? evt.data : String(evt.data));
|
|
656
|
+
events.push(event);
|
|
657
|
+
if (resolve) {
|
|
658
|
+
const r = resolve;
|
|
659
|
+
resolve = null;
|
|
660
|
+
r();
|
|
661
|
+
}
|
|
662
|
+
} catch {}
|
|
663
|
+
};
|
|
664
|
+
ws.onerror = () => {
|
|
665
|
+
if (!done) {
|
|
666
|
+
wsError = new Error("WebSocket connection failed");
|
|
667
|
+
done = true;
|
|
668
|
+
if (resolve) {
|
|
669
|
+
const r = resolve;
|
|
670
|
+
resolve = null;
|
|
671
|
+
r();
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
ws.onclose = () => {
|
|
676
|
+
done = true;
|
|
677
|
+
if (resolve) {
|
|
678
|
+
const r = resolve;
|
|
679
|
+
resolve = null;
|
|
680
|
+
r();
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
try {
|
|
684
|
+
while (true) {
|
|
685
|
+
await waitForEvent();
|
|
686
|
+
if (wsError) {
|
|
687
|
+
throw wsError;
|
|
688
|
+
}
|
|
689
|
+
while (events.length > 0) {
|
|
690
|
+
yield events.shift();
|
|
691
|
+
}
|
|
692
|
+
if (done) {
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
} finally {
|
|
697
|
+
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
698
|
+
ws.close();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
async* executeStreamSse(req) {
|
|
607
703
|
const res = await this.fetch("/execute/stream", {
|
|
608
704
|
method: "POST",
|
|
609
705
|
body: JSON.stringify({
|
|
@@ -752,6 +848,11 @@ var DEFAULT_CONFIG = {
|
|
|
752
848
|
includeCode: false,
|
|
753
849
|
includeOutput: false
|
|
754
850
|
},
|
|
851
|
+
auth: {
|
|
852
|
+
enabled: false,
|
|
853
|
+
defaultTtlMs: 86400000,
|
|
854
|
+
cleanupIntervalMs: 3600000
|
|
855
|
+
},
|
|
755
856
|
debug: false
|
|
756
857
|
};
|
|
757
858
|
function loadConfig(cwd) {
|
|
@@ -805,6 +906,10 @@ function mergeConfig(defaults, overrides) {
|
|
|
805
906
|
...defaults.audit,
|
|
806
907
|
...overrides.audit
|
|
807
908
|
},
|
|
909
|
+
auth: {
|
|
910
|
+
...defaults.auth,
|
|
911
|
+
...overrides.auth
|
|
912
|
+
},
|
|
808
913
|
debug: overrides.debug ?? defaults.debug
|
|
809
914
|
};
|
|
810
915
|
}
|
|
@@ -848,7 +953,6 @@ init_runtime();
|
|
|
848
953
|
init_logger();
|
|
849
954
|
import { randomUUID } from "node:crypto";
|
|
850
955
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
851
|
-
import { PassThrough } from "node:stream";
|
|
852
956
|
import Docker from "dockerode";
|
|
853
957
|
|
|
854
958
|
// src/engine/audit.ts
|
|
@@ -1228,137 +1332,609 @@ var EMBEDDED_DEFAULT_SECCOMP_PROFILE = JSON.stringify({
|
|
|
1228
1332
|
// src/engine/docker.ts
|
|
1229
1333
|
init_image_builder();
|
|
1230
1334
|
|
|
1231
|
-
// src/engine/
|
|
1335
|
+
// src/engine/managers/execution-manager.ts
|
|
1232
1336
|
init_logger();
|
|
1337
|
+
import { PassThrough } from "node:stream";
|
|
1233
1338
|
|
|
1234
|
-
class
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
cleanPoolSize;
|
|
1238
|
-
dirtyPoolSize;
|
|
1239
|
-
createOptions;
|
|
1240
|
-
networkMode;
|
|
1241
|
-
securityMode;
|
|
1242
|
-
pools = new Map;
|
|
1243
|
-
replenishing = new Set;
|
|
1244
|
-
pendingReplenishments = new Set;
|
|
1245
|
-
cleaningInterval = null;
|
|
1339
|
+
class ExecutionManager {
|
|
1340
|
+
secrets;
|
|
1341
|
+
maxOutputSize;
|
|
1246
1342
|
constructor(options) {
|
|
1247
|
-
this.
|
|
1248
|
-
this.
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1343
|
+
this.secrets = options.secrets;
|
|
1344
|
+
this.maxOutputSize = options.maxOutputSize;
|
|
1345
|
+
}
|
|
1346
|
+
wrapWithTimeout(cmd, timeoutSec) {
|
|
1347
|
+
return ["timeout", "-s", "KILL", String(timeoutSec), ...cmd];
|
|
1348
|
+
}
|
|
1349
|
+
getInstallCommand(runtime, packages) {
|
|
1350
|
+
switch (runtime) {
|
|
1351
|
+
case "python":
|
|
1352
|
+
return [
|
|
1353
|
+
"pip",
|
|
1354
|
+
"install",
|
|
1355
|
+
"--user",
|
|
1356
|
+
"--no-cache-dir",
|
|
1357
|
+
"--break-system-packages",
|
|
1358
|
+
"--disable-pip-version-check",
|
|
1359
|
+
"--retries",
|
|
1360
|
+
"0",
|
|
1361
|
+
"--timeout",
|
|
1362
|
+
"15",
|
|
1363
|
+
...packages
|
|
1364
|
+
];
|
|
1365
|
+
case "node":
|
|
1366
|
+
return ["npm", "install", "--prefix", "/sandbox", ...packages];
|
|
1367
|
+
case "bun":
|
|
1368
|
+
return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
|
|
1369
|
+
case "deno":
|
|
1370
|
+
return ["sh", "-c", packages.map((p) => `deno cache ${p}`).join(" && ")];
|
|
1371
|
+
case "bash":
|
|
1372
|
+
return ["apk", "add", "--no-cache", ...packages];
|
|
1373
|
+
default:
|
|
1374
|
+
throw new Error(`Unknown runtime for package install: ${runtime}`);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
async installPackages(container, runtime, packages, timeoutMs) {
|
|
1378
|
+
const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
1379
|
+
const cmd = this.wrapWithTimeout(this.getInstallCommand(runtime, packages), timeoutSec);
|
|
1380
|
+
logger.debug(`Installing packages: ${JSON.stringify(cmd)}`);
|
|
1381
|
+
const env = [
|
|
1382
|
+
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
|
|
1383
|
+
];
|
|
1384
|
+
if (runtime === "python") {
|
|
1385
|
+
env.push("PYTHONUSERBASE=/sandbox/.local");
|
|
1386
|
+
} else if (runtime === "node") {
|
|
1387
|
+
env.push("NPM_CONFIG_PREFIX=/sandbox/.npm-global");
|
|
1388
|
+
env.push("NPM_CONFIG_CACHE=/sandbox/.npm-cache");
|
|
1389
|
+
env.push("npm_config_cache=/sandbox/.npm-cache");
|
|
1390
|
+
env.push("NPM_CONFIG_FETCH_RETRIES=0");
|
|
1391
|
+
env.push("npm_config_fetch_retries=0");
|
|
1392
|
+
env.push("NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=1000");
|
|
1393
|
+
env.push("npm_config_fetch_retry_mintimeout=1000");
|
|
1394
|
+
env.push("NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=2000");
|
|
1395
|
+
env.push("npm_config_fetch_retry_maxtimeout=2000");
|
|
1396
|
+
} else if (runtime === "bun") {
|
|
1397
|
+
env.push("BUN_INSTALL_GLOBAL_DIR=/sandbox/.bun-global");
|
|
1398
|
+
env.push("BUN_INSTALL_CACHE_DIR=/sandbox/.bun-cache");
|
|
1399
|
+
env.push("BUN_INSTALL_BIN=/sandbox/.bun-global/bin");
|
|
1400
|
+
} else if (runtime === "deno") {
|
|
1401
|
+
env.push("DENO_DIR=/sandbox/.deno");
|
|
1264
1402
|
}
|
|
1403
|
+
const exec = await container.exec({
|
|
1404
|
+
Cmd: cmd,
|
|
1405
|
+
AttachStdout: true,
|
|
1406
|
+
AttachStderr: true,
|
|
1407
|
+
Env: env,
|
|
1408
|
+
User: runtime === "bash" ? "root" : "sandbox"
|
|
1409
|
+
});
|
|
1410
|
+
const stream = await exec.start({ Detach: false, Tty: false });
|
|
1411
|
+
return new Promise((resolve2, reject) => {
|
|
1412
|
+
let stderr = "";
|
|
1413
|
+
const stdoutStream = new PassThrough;
|
|
1414
|
+
const stderrStream = new PassThrough;
|
|
1415
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1416
|
+
stderrStream.on("data", (chunk) => {
|
|
1417
|
+
const text = chunk.toString();
|
|
1418
|
+
stderr += text;
|
|
1419
|
+
logger.debug(`[install:${runtime}:stderr] ${text.trimEnd()}`);
|
|
1420
|
+
});
|
|
1421
|
+
stdoutStream.on("data", (chunk) => {
|
|
1422
|
+
const text = chunk.toString();
|
|
1423
|
+
logger.debug(`[install:${runtime}:stdout] ${text.trimEnd()}`);
|
|
1424
|
+
});
|
|
1425
|
+
stream.on("end", async () => {
|
|
1426
|
+
try {
|
|
1427
|
+
const info = await exec.inspect();
|
|
1428
|
+
if (info.ExitCode !== 0) {
|
|
1429
|
+
reject(new Error(`Package install failed (exit code ${info.ExitCode}): ${stderr}`));
|
|
1430
|
+
} else {
|
|
1431
|
+
resolve2();
|
|
1432
|
+
}
|
|
1433
|
+
} catch (err) {
|
|
1434
|
+
reject(err);
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
stream.on("error", reject);
|
|
1438
|
+
});
|
|
1265
1439
|
}
|
|
1266
|
-
async
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1440
|
+
async* streamExecOutput(stream, exec, container, timeoutMs) {
|
|
1441
|
+
const queue = [];
|
|
1442
|
+
let resolve2 = null;
|
|
1443
|
+
let done = false;
|
|
1444
|
+
const push = (event) => {
|
|
1445
|
+
queue.push(event);
|
|
1446
|
+
if (resolve2) {
|
|
1447
|
+
resolve2();
|
|
1448
|
+
resolve2 = null;
|
|
1274
1449
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1450
|
+
};
|
|
1451
|
+
const timer = setTimeout(() => {
|
|
1452
|
+
push({ type: "error", data: "EXECUTION TIMED OUT" });
|
|
1453
|
+
push({ type: "exit", data: "137" });
|
|
1454
|
+
done = true;
|
|
1455
|
+
}, timeoutMs);
|
|
1456
|
+
const stdoutStream = new PassThrough;
|
|
1457
|
+
const stderrStream = new PassThrough;
|
|
1458
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1459
|
+
stdoutStream.on("data", (chunk) => {
|
|
1460
|
+
let text = chunk.toString("utf-8");
|
|
1461
|
+
if (Object.keys(this.secrets).length > 0) {
|
|
1462
|
+
text = maskSecrets(text, this.secrets);
|
|
1284
1463
|
}
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
this.
|
|
1290
|
-
|
|
1291
|
-
this.replenish(image);
|
|
1292
|
-
return entry.container;
|
|
1293
|
-
}
|
|
1294
|
-
return this.createContainer(image);
|
|
1295
|
-
}
|
|
1296
|
-
async release(container, image) {
|
|
1297
|
-
let pool = this.pools.get(image);
|
|
1298
|
-
if (!pool) {
|
|
1299
|
-
pool = { clean: [], dirty: [] };
|
|
1300
|
-
this.pools.set(image, pool);
|
|
1301
|
-
}
|
|
1302
|
-
if (this.poolStrategy === "fast") {
|
|
1303
|
-
if (pool.dirty.length >= this.dirtyPoolSize) {
|
|
1304
|
-
await container.remove({ force: true }).catch(() => {});
|
|
1305
|
-
return;
|
|
1464
|
+
push({ type: "stdout", data: text });
|
|
1465
|
+
});
|
|
1466
|
+
stderrStream.on("data", (chunk) => {
|
|
1467
|
+
let text = chunk.toString("utf-8");
|
|
1468
|
+
if (Object.keys(this.secrets).length > 0) {
|
|
1469
|
+
text = maskSecrets(text, this.secrets);
|
|
1306
1470
|
}
|
|
1307
|
-
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1471
|
+
push({ type: "stderr", data: text });
|
|
1472
|
+
});
|
|
1473
|
+
stream.on("end", async () => {
|
|
1474
|
+
clearTimeout(timer);
|
|
1475
|
+
try {
|
|
1476
|
+
const info = await exec.inspect();
|
|
1477
|
+
push({ type: "exit", data: (info.ExitCode ?? 0).toString() });
|
|
1478
|
+
} catch {
|
|
1479
|
+
push({ type: "exit", data: "1" });
|
|
1312
1480
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1481
|
+
done = true;
|
|
1482
|
+
});
|
|
1483
|
+
stream.on("error", (err) => {
|
|
1484
|
+
clearTimeout(timer);
|
|
1485
|
+
push({ type: "error", data: err.message });
|
|
1486
|
+
push({ type: "exit", data: "1" });
|
|
1487
|
+
done = true;
|
|
1488
|
+
});
|
|
1489
|
+
while (!done || queue.length > 0) {
|
|
1490
|
+
if (queue.length > 0) {
|
|
1491
|
+
yield queue.shift();
|
|
1492
|
+
} else if (resolve2) {
|
|
1493
|
+
await new Promise((r) => {
|
|
1494
|
+
resolve2 = r;
|
|
1495
|
+
});
|
|
1496
|
+
} else {
|
|
1497
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1315
1498
|
}
|
|
1316
|
-
pool.clean.push({ container, createdAt: Date.now() });
|
|
1317
1499
|
}
|
|
1318
1500
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1501
|
+
async collectExecOutput(stream, container, timeoutMs) {
|
|
1502
|
+
return new Promise((resolve2, reject) => {
|
|
1503
|
+
let stdout = "";
|
|
1504
|
+
let stderr = "";
|
|
1505
|
+
let truncated = false;
|
|
1506
|
+
let settled = false;
|
|
1507
|
+
let stdoutEnded = false;
|
|
1508
|
+
let stderrEnded = false;
|
|
1509
|
+
const timer = setTimeout(() => {
|
|
1510
|
+
if (settled) {
|
|
1511
|
+
return;
|
|
1332
1512
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1513
|
+
settled = true;
|
|
1514
|
+
if (stream.destroy) {
|
|
1515
|
+
stream.destroy();
|
|
1516
|
+
}
|
|
1517
|
+
resolve2({ stdout, stderr: `${stderr}
|
|
1518
|
+
--- EXECUTION TIMED OUT ---`, truncated });
|
|
1519
|
+
}, timeoutMs);
|
|
1520
|
+
const stdoutStream = new PassThrough;
|
|
1521
|
+
const stderrStream = new PassThrough;
|
|
1522
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1523
|
+
stdoutStream.on("data", (chunk) => {
|
|
1524
|
+
stdout += chunk.toString("utf-8");
|
|
1525
|
+
if (stdout.length > this.maxOutputSize) {
|
|
1526
|
+
const result = truncateOutput(stdout, this.maxOutputSize);
|
|
1527
|
+
stdout = result.text;
|
|
1528
|
+
truncated = true;
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
stderrStream.on("data", (chunk) => {
|
|
1532
|
+
stderr += chunk.toString("utf-8");
|
|
1533
|
+
if (stderr.length > this.maxOutputSize) {
|
|
1534
|
+
const result = truncateOutput(stderr, this.maxOutputSize);
|
|
1535
|
+
stderr = result.text;
|
|
1536
|
+
truncated = true;
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
const checkDone = () => {
|
|
1540
|
+
if (settled) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (stdoutEnded && stderrEnded) {
|
|
1544
|
+
settled = true;
|
|
1545
|
+
clearTimeout(timer);
|
|
1546
|
+
resolve2({ stdout, stderr, truncated });
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
stdoutStream.on("end", () => {
|
|
1550
|
+
stdoutEnded = true;
|
|
1551
|
+
checkDone();
|
|
1552
|
+
});
|
|
1553
|
+
stderrStream.on("end", () => {
|
|
1554
|
+
stderrEnded = true;
|
|
1555
|
+
checkDone();
|
|
1556
|
+
});
|
|
1557
|
+
stream.on("error", (err) => {
|
|
1558
|
+
if (settled) {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
settled = true;
|
|
1562
|
+
clearTimeout(timer);
|
|
1563
|
+
reject(err);
|
|
1564
|
+
});
|
|
1565
|
+
stream.on("end", () => {
|
|
1566
|
+
if (settled) {
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
setTimeout(() => {
|
|
1570
|
+
if (!settled) {
|
|
1571
|
+
stdoutEnded = true;
|
|
1572
|
+
stderrEnded = true;
|
|
1573
|
+
checkDone();
|
|
1574
|
+
}
|
|
1575
|
+
}, 100);
|
|
1576
|
+
});
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
postProcessOutput(output, _truncated) {
|
|
1580
|
+
let result = output;
|
|
1581
|
+
if (Object.keys(this.secrets).length > 0) {
|
|
1582
|
+
result = maskSecrets(result, this.secrets);
|
|
1583
|
+
}
|
|
1584
|
+
return result.trimEnd();
|
|
1585
|
+
}
|
|
1586
|
+
buildEnv(extra, proxyPort, networkMode, networkFilter) {
|
|
1587
|
+
const env = [
|
|
1588
|
+
"PYTHONUNBUFFERED=1",
|
|
1589
|
+
"PYTHONUSERBASE=/sandbox/.local",
|
|
1590
|
+
"NPM_CONFIG_PREFIX=/sandbox/.npm-global",
|
|
1591
|
+
"DENO_DIR=/sandbox/.deno",
|
|
1592
|
+
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin",
|
|
1593
|
+
"NODE_PATH=/usr/local/lib/node_modules:/sandbox/.npm-global/lib/node_modules:/sandbox/node_modules"
|
|
1594
|
+
];
|
|
1595
|
+
for (const [key, value] of Object.entries(this.secrets)) {
|
|
1596
|
+
env.push(`${key}=${value}`);
|
|
1597
|
+
}
|
|
1598
|
+
if (extra) {
|
|
1599
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
1600
|
+
env.push(`${key}=${value}`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
if (networkMode === "filtered") {
|
|
1604
|
+
if (networkFilter) {
|
|
1605
|
+
env.push(`ISOL8_WHITELIST=${JSON.stringify(networkFilter.whitelist)}`);
|
|
1606
|
+
env.push(`ISOL8_BLACKLIST=${JSON.stringify(networkFilter.blacklist)}`);
|
|
1607
|
+
}
|
|
1608
|
+
if (proxyPort) {
|
|
1609
|
+
env.push(`HTTP_PROXY=http://127.0.0.1:${proxyPort}`);
|
|
1610
|
+
env.push(`HTTPS_PROXY=http://127.0.0.1:${proxyPort}`);
|
|
1611
|
+
env.push(`http_proxy=http://127.0.0.1:${proxyPort}`);
|
|
1612
|
+
env.push(`https_proxy=http://127.0.0.1:${proxyPort}`);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
return env;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
// src/engine/managers/network-manager.ts
|
|
1619
|
+
init_logger();
|
|
1620
|
+
var PROXY_PORT = 8118;
|
|
1621
|
+
var PROXY_STARTUP_TIMEOUT_MS = 5000;
|
|
1622
|
+
var PROXY_POLL_INTERVAL_MS = 100;
|
|
1623
|
+
|
|
1624
|
+
class NetworkManager {
|
|
1625
|
+
network;
|
|
1626
|
+
networkFilter;
|
|
1627
|
+
constructor(options) {
|
|
1628
|
+
this.network = options.network;
|
|
1629
|
+
this.networkFilter = options.networkFilter;
|
|
1630
|
+
}
|
|
1631
|
+
async startProxy(container) {
|
|
1632
|
+
if (this.network !== "filtered") {
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
const envParts = [];
|
|
1636
|
+
if (this.networkFilter) {
|
|
1637
|
+
envParts.push(`ISOL8_WHITELIST='${JSON.stringify(this.networkFilter.whitelist)}'`);
|
|
1638
|
+
envParts.push(`ISOL8_BLACKLIST='${JSON.stringify(this.networkFilter.blacklist)}'`);
|
|
1639
|
+
}
|
|
1640
|
+
const envPrefix = envParts.length > 0 ? `${envParts.join(" ")} ` : "";
|
|
1641
|
+
const startExec = await container.exec({
|
|
1642
|
+
Cmd: ["sh", "-c", `${envPrefix}bash /usr/local/bin/proxy.sh &`]
|
|
1643
|
+
});
|
|
1644
|
+
await startExec.start({ Detach: true });
|
|
1645
|
+
const deadline = Date.now() + PROXY_STARTUP_TIMEOUT_MS;
|
|
1646
|
+
while (Date.now() < deadline) {
|
|
1647
|
+
try {
|
|
1648
|
+
const checkExec = await container.exec({
|
|
1649
|
+
Cmd: ["sh", "-c", `nc -z 127.0.0.1 ${PROXY_PORT} 2>/dev/null`]
|
|
1650
|
+
});
|
|
1651
|
+
await checkExec.start({ Detach: true });
|
|
1652
|
+
let info = await checkExec.inspect();
|
|
1653
|
+
while (info.Running) {
|
|
1654
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1655
|
+
info = await checkExec.inspect();
|
|
1656
|
+
}
|
|
1657
|
+
if (info.ExitCode === 0) {
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
} catch {}
|
|
1661
|
+
await new Promise((r) => setTimeout(r, PROXY_POLL_INTERVAL_MS));
|
|
1662
|
+
}
|
|
1663
|
+
throw new Error("Proxy failed to start within timeout");
|
|
1664
|
+
}
|
|
1665
|
+
async setupIptables(container) {
|
|
1666
|
+
if (this.network !== "filtered") {
|
|
1667
|
+
return;
|
|
1668
|
+
}
|
|
1669
|
+
const rules = [
|
|
1670
|
+
"/usr/sbin/iptables -A OUTPUT -o lo -j ACCEPT",
|
|
1671
|
+
"/usr/sbin/iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT",
|
|
1672
|
+
`/usr/sbin/iptables -A OUTPUT -p tcp -d 127.0.0.1 --dport ${PROXY_PORT} -m owner --uid-owner 100 -j ACCEPT`,
|
|
1673
|
+
"/usr/sbin/iptables -A OUTPUT -m owner --uid-owner 100 -j DROP"
|
|
1674
|
+
].join(" && ");
|
|
1675
|
+
const exec = await container.exec({
|
|
1676
|
+
Cmd: ["sh", "-c", rules]
|
|
1677
|
+
});
|
|
1678
|
+
await exec.start({ Detach: true });
|
|
1679
|
+
let info = await exec.inspect();
|
|
1680
|
+
while (info.Running) {
|
|
1681
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1682
|
+
info = await exec.inspect();
|
|
1683
|
+
}
|
|
1684
|
+
if (info.ExitCode !== 0) {
|
|
1685
|
+
throw new Error(`Failed to set up iptables rules (exit code ${info.ExitCode})`);
|
|
1686
|
+
}
|
|
1687
|
+
logger.debug("[Filtered] iptables rules applied — sandbox user restricted to proxy only");
|
|
1688
|
+
}
|
|
1689
|
+
get proxyPort() {
|
|
1690
|
+
return PROXY_PORT;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
// src/engine/managers/volume-manager.ts
|
|
1694
|
+
import { PassThrough as PassThrough2 } from "node:stream";
|
|
1695
|
+
|
|
1696
|
+
class VolumeManager {
|
|
1697
|
+
readonlyRootFs;
|
|
1698
|
+
sandboxWorkdir;
|
|
1699
|
+
constructor(options) {
|
|
1700
|
+
this.readonlyRootFs = options.readonlyRootFs;
|
|
1701
|
+
this.sandboxWorkdir = options.sandboxWorkdir ?? "/sandbox";
|
|
1702
|
+
}
|
|
1703
|
+
async writeFileViaExec(container, filePath, content) {
|
|
1704
|
+
const data = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
1705
|
+
const b64 = data.toString("base64");
|
|
1706
|
+
if (b64.length < 20000) {
|
|
1707
|
+
const exec = await container.exec({
|
|
1708
|
+
Cmd: ["sh", "-c", `printf '%s' '${b64}' | base64 -d > ${filePath}`],
|
|
1709
|
+
User: "sandbox"
|
|
1710
|
+
});
|
|
1711
|
+
await exec.start({ Detach: true });
|
|
1712
|
+
let info2 = await exec.inspect();
|
|
1713
|
+
while (info2.Running) {
|
|
1714
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1715
|
+
info2 = await exec.inspect();
|
|
1716
|
+
}
|
|
1717
|
+
if (info2.ExitCode !== 0) {
|
|
1718
|
+
throw new Error(`Failed to write file ${filePath} in container (exit code ${info2.ExitCode})`);
|
|
1719
|
+
}
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
const tempPath = `/tmp/b64_${Date.now()}.tmp`;
|
|
1723
|
+
const chunkSize = 8000;
|
|
1724
|
+
for (let i = 0;i < b64.length; i += chunkSize) {
|
|
1725
|
+
const chunk = b64.slice(i, i + chunkSize);
|
|
1726
|
+
const exec = await container.exec({
|
|
1727
|
+
Cmd: ["sh", "-c", `printf '%s' '${chunk}' >> ${tempPath}`],
|
|
1728
|
+
User: "sandbox"
|
|
1729
|
+
});
|
|
1730
|
+
await exec.start({ Detach: true });
|
|
1731
|
+
await exec.inspect();
|
|
1732
|
+
}
|
|
1733
|
+
const decodeExec = await container.exec({
|
|
1734
|
+
Cmd: ["sh", "-c", `base64 -d ${tempPath} > ${filePath} && rm ${tempPath}`],
|
|
1735
|
+
User: "sandbox"
|
|
1736
|
+
});
|
|
1737
|
+
await decodeExec.start({ Detach: true });
|
|
1738
|
+
let info = await decodeExec.inspect();
|
|
1739
|
+
while (info.Running) {
|
|
1740
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1741
|
+
info = await decodeExec.inspect();
|
|
1742
|
+
}
|
|
1743
|
+
if (info.ExitCode !== 0) {
|
|
1744
|
+
throw new Error(`Failed to write file ${filePath} in container (exit code ${info.ExitCode})`);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
async readFileViaExec(container, filePath) {
|
|
1748
|
+
const exec = await container.exec({
|
|
1749
|
+
Cmd: ["base64", filePath],
|
|
1750
|
+
AttachStdout: true,
|
|
1751
|
+
AttachStderr: true,
|
|
1752
|
+
User: "sandbox"
|
|
1753
|
+
});
|
|
1754
|
+
const stream = await exec.start({ Tty: false });
|
|
1755
|
+
const chunks = [];
|
|
1756
|
+
const stderrChunks = [];
|
|
1757
|
+
const stdoutStream = new PassThrough2;
|
|
1758
|
+
const stderrStream = new PassThrough2;
|
|
1759
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1760
|
+
stdoutStream.on("data", (chunk) => chunks.push(chunk));
|
|
1761
|
+
stderrStream.on("data", (chunk) => stderrChunks.push(chunk));
|
|
1762
|
+
await new Promise((resolve2, reject) => {
|
|
1763
|
+
stream.on("end", resolve2);
|
|
1764
|
+
stream.on("error", reject);
|
|
1765
|
+
});
|
|
1766
|
+
const inspectResult = await exec.inspect();
|
|
1767
|
+
if (inspectResult.ExitCode !== 0) {
|
|
1768
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
|
|
1769
|
+
throw new Error(`Failed to read file ${filePath} in container: ${stderr} (exit code ${inspectResult.ExitCode})`);
|
|
1770
|
+
}
|
|
1771
|
+
const b64Output = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1772
|
+
return Buffer.from(b64Output, "base64");
|
|
1773
|
+
}
|
|
1774
|
+
async getFileFromContainer(container, path) {
|
|
1775
|
+
const stream = await container.getArchive({ path });
|
|
1776
|
+
const chunks = [];
|
|
1777
|
+
for await (const chunk of stream) {
|
|
1778
|
+
chunks.push(chunk);
|
|
1779
|
+
}
|
|
1780
|
+
return extractFromTar(Buffer.concat(chunks), path);
|
|
1781
|
+
}
|
|
1782
|
+
async retrieveFiles(container, paths) {
|
|
1783
|
+
const files = {};
|
|
1784
|
+
for (const p of paths) {
|
|
1785
|
+
try {
|
|
1786
|
+
const buf = this.readonlyRootFs ? await this.readFileViaExec(container, p) : await this.getFileFromContainer(container, p);
|
|
1787
|
+
files[p] = buf.toString("base64");
|
|
1788
|
+
} catch {}
|
|
1789
|
+
}
|
|
1790
|
+
return files;
|
|
1791
|
+
}
|
|
1792
|
+
async putFile(container, path, content) {
|
|
1793
|
+
if (this.readonlyRootFs) {
|
|
1794
|
+
await this.writeFileViaExec(container, path, content);
|
|
1795
|
+
} else {
|
|
1796
|
+
const tar = createTarBuffer(path, content);
|
|
1797
|
+
await container.putArchive(tar, { path: "/" });
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
async getFile(container, path) {
|
|
1801
|
+
if (this.readonlyRootFs) {
|
|
1802
|
+
return this.readFileViaExec(container, path);
|
|
1803
|
+
}
|
|
1804
|
+
return this.getFileFromContainer(container, path);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
// src/engine/pool.ts
|
|
1808
|
+
init_logger();
|
|
1809
|
+
|
|
1810
|
+
class ContainerPool {
|
|
1811
|
+
docker;
|
|
1812
|
+
poolStrategy;
|
|
1813
|
+
cleanPoolSize;
|
|
1814
|
+
dirtyPoolSize;
|
|
1815
|
+
createOptions;
|
|
1816
|
+
networkMode;
|
|
1817
|
+
securityMode;
|
|
1818
|
+
pools = new Map;
|
|
1819
|
+
replenishing = new Set;
|
|
1820
|
+
pendingReplenishments = new Set;
|
|
1821
|
+
cleaningInterval = null;
|
|
1822
|
+
constructor(options) {
|
|
1823
|
+
this.docker = options.docker;
|
|
1824
|
+
this.poolStrategy = options.poolStrategy ?? "fast";
|
|
1825
|
+
this.createOptions = options.createOptions;
|
|
1826
|
+
this.networkMode = options.networkMode;
|
|
1827
|
+
this.securityMode = options.securityMode;
|
|
1828
|
+
if (typeof options.poolSize === "number") {
|
|
1829
|
+
this.cleanPoolSize = options.poolSize;
|
|
1830
|
+
this.dirtyPoolSize = options.poolSize;
|
|
1831
|
+
} else if (options.poolSize) {
|
|
1832
|
+
this.cleanPoolSize = options.poolSize.clean ?? 1;
|
|
1833
|
+
this.dirtyPoolSize = options.poolSize.dirty ?? 1;
|
|
1834
|
+
} else {
|
|
1835
|
+
this.cleanPoolSize = 1;
|
|
1836
|
+
this.dirtyPoolSize = 1;
|
|
1837
|
+
}
|
|
1838
|
+
if (this.poolStrategy === "fast") {
|
|
1839
|
+
this.startBackgroundCleaning();
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
async acquire(image) {
|
|
1843
|
+
const pool = this.pools.get(image) ?? { clean: [], dirty: [] };
|
|
1844
|
+
if (this.poolStrategy === "fast") {
|
|
1845
|
+
if (pool.clean.length > 0) {
|
|
1846
|
+
const entry = pool.clean.shift();
|
|
1847
|
+
this.pools.set(image, pool);
|
|
1848
|
+
this.replenish(image);
|
|
1849
|
+
return entry.container;
|
|
1850
|
+
}
|
|
1851
|
+
if (pool.dirty.length > 0 && pool.clean.length < this.cleanPoolSize) {
|
|
1852
|
+
await this.cleanDirtyImmediate(image);
|
|
1853
|
+
const updatedPool = this.pools.get(image);
|
|
1854
|
+
if (updatedPool && updatedPool.clean.length > 0) {
|
|
1855
|
+
const entry = updatedPool.clean.shift();
|
|
1856
|
+
this.pools.set(image, updatedPool);
|
|
1857
|
+
this.replenish(image);
|
|
1858
|
+
return entry.container;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return this.createContainer(image);
|
|
1862
|
+
}
|
|
1863
|
+
if (pool.clean && pool.clean.length > 0) {
|
|
1864
|
+
const entry = pool.clean.shift();
|
|
1865
|
+
this.pools.set(image, { clean: pool.clean, dirty: [] });
|
|
1866
|
+
await this.cleanupContainer(entry.container);
|
|
1867
|
+
this.replenish(image);
|
|
1868
|
+
return entry.container;
|
|
1869
|
+
}
|
|
1870
|
+
return this.createContainer(image);
|
|
1871
|
+
}
|
|
1872
|
+
async release(container, image) {
|
|
1873
|
+
let pool = this.pools.get(image);
|
|
1874
|
+
if (!pool) {
|
|
1875
|
+
pool = { clean: [], dirty: [] };
|
|
1876
|
+
this.pools.set(image, pool);
|
|
1877
|
+
}
|
|
1878
|
+
if (this.poolStrategy === "fast") {
|
|
1879
|
+
if (pool.dirty.length >= this.dirtyPoolSize) {
|
|
1880
|
+
await container.remove({ force: true }).catch(() => {});
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
pool.dirty.push({ container, createdAt: Date.now() });
|
|
1884
|
+
} else {
|
|
1885
|
+
if (pool.clean.length >= this.cleanPoolSize) {
|
|
1886
|
+
await container.remove({ force: true }).catch(() => {});
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
if (!pool.clean) {
|
|
1890
|
+
pool.clean = [];
|
|
1891
|
+
}
|
|
1892
|
+
pool.clean.push({ container, createdAt: Date.now() });
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
startBackgroundCleaning() {
|
|
1896
|
+
this.cleaningInterval = setInterval(async () => {
|
|
1897
|
+
for (const [_image, pool] of this.pools) {
|
|
1898
|
+
for (let i = 0;i < this.dirtyPoolSize; i++) {
|
|
1899
|
+
if (pool.dirty.length > 0 && pool.clean.length < this.cleanPoolSize) {
|
|
1900
|
+
const entry = pool.dirty.shift();
|
|
1901
|
+
try {
|
|
1902
|
+
await this.cleanupContainer(entry.container);
|
|
1903
|
+
pool.clean.push(entry);
|
|
1904
|
+
} catch {
|
|
1905
|
+
entry.container.remove({ force: true }).catch(() => {});
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}, 5000);
|
|
1911
|
+
}
|
|
1912
|
+
async cleanDirtyImmediate(image) {
|
|
1913
|
+
const pool = this.pools.get(image);
|
|
1914
|
+
if (!pool || pool.dirty.length === 0 || pool.clean.length >= this.cleanPoolSize) {
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
const entry = pool.dirty.shift();
|
|
1918
|
+
try {
|
|
1919
|
+
await this.cleanupContainer(entry.container);
|
|
1920
|
+
pool.clean.push(entry);
|
|
1921
|
+
} catch {
|
|
1922
|
+
entry.container.remove({ force: true }).catch(() => {});
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
async cleanupContainer(container) {
|
|
1926
|
+
const needsCleanup = this.securityMode === "strict";
|
|
1927
|
+
const needsIptables = this.networkMode === "filtered" && needsCleanup;
|
|
1928
|
+
if (!needsCleanup) {
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
try {
|
|
1932
|
+
const cleanupCmd = needsIptables ? "pkill -9 -u sandbox 2>/dev/null; /usr/sbin/iptables -F OUTPUT 2>/dev/null; rm -rf /sandbox/* /sandbox/.[!.]* 2>/dev/null; true" : "pkill -9 -u sandbox 2>/dev/null; rm -rf /sandbox/* /sandbox/.[!.]* 2>/dev/null; true";
|
|
1933
|
+
const cleanExec = await container.exec({
|
|
1934
|
+
Cmd: ["sh", "-c", cleanupCmd]
|
|
1935
|
+
});
|
|
1936
|
+
await cleanExec.start({ Detach: true });
|
|
1937
|
+
let info = await cleanExec.inspect();
|
|
1362
1938
|
while (info.Running) {
|
|
1363
1939
|
await new Promise((r) => setTimeout(r, 5));
|
|
1364
1940
|
info = await cleanExec.inspect();
|
|
@@ -1471,265 +2047,45 @@ function calculateCPUPercent(stats) {
|
|
|
1471
2047
|
const numCores = stats.cpu_stats.online_cpus ?? stats.cpu_stats.cpu_usage.percpu_usage?.length ?? 1;
|
|
1472
2048
|
return cpuDelta / systemDelta * numCores * 100;
|
|
1473
2049
|
}
|
|
1474
|
-
function calculateNetworkStats(stats) {
|
|
1475
|
-
if (!stats.networks) {
|
|
1476
|
-
return { in: 0, out: 0 };
|
|
1477
|
-
}
|
|
1478
|
-
let rxBytes = 0;
|
|
1479
|
-
let txBytes = 0;
|
|
1480
|
-
for (const iface of Object.values(stats.networks)) {
|
|
1481
|
-
rxBytes += iface.rx_bytes;
|
|
1482
|
-
txBytes += iface.tx_bytes;
|
|
1483
|
-
}
|
|
1484
|
-
return { in: rxBytes, out: txBytes };
|
|
1485
|
-
}
|
|
1486
|
-
async function getContainerStats(container) {
|
|
1487
|
-
const stats = await container.stats({
|
|
1488
|
-
stream: false
|
|
1489
|
-
});
|
|
1490
|
-
const cpuPercent = calculateCPUPercent(stats);
|
|
1491
|
-
const memoryBytes = stats.memory_stats.usage;
|
|
1492
|
-
const network = calculateNetworkStats(stats);
|
|
1493
|
-
return {
|
|
1494
|
-
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
|
1495
|
-
memoryMB: Math.round(memoryBytes / (1024 * 1024)),
|
|
1496
|
-
networkBytesIn: network.in,
|
|
1497
|
-
networkBytesOut: network.out
|
|
1498
|
-
};
|
|
1499
|
-
}
|
|
1500
|
-
function calculateResourceDelta(before, after) {
|
|
1501
|
-
return {
|
|
1502
|
-
cpuPercent: after.cpuPercent,
|
|
1503
|
-
memoryMB: after.memoryMB,
|
|
1504
|
-
networkBytesIn: after.networkBytesIn - before.networkBytesIn,
|
|
1505
|
-
networkBytesOut: after.networkBytesOut - before.networkBytesOut
|
|
1506
|
-
};
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// src/engine/docker.ts
|
|
1510
|
-
async function writeFileViaExec(container, filePath, content) {
|
|
1511
|
-
const data = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
1512
|
-
const b64 = data.toString("base64");
|
|
1513
|
-
if (b64.length < 20000) {
|
|
1514
|
-
const exec = await container.exec({
|
|
1515
|
-
Cmd: ["sh", "-c", `printf '%s' '${b64}' | base64 -d > ${filePath}`],
|
|
1516
|
-
User: "sandbox"
|
|
1517
|
-
});
|
|
1518
|
-
await exec.start({ Detach: true });
|
|
1519
|
-
let info2 = await exec.inspect();
|
|
1520
|
-
while (info2.Running) {
|
|
1521
|
-
await new Promise((r) => setTimeout(r, 5));
|
|
1522
|
-
info2 = await exec.inspect();
|
|
1523
|
-
}
|
|
1524
|
-
if (info2.ExitCode !== 0) {
|
|
1525
|
-
throw new Error(`Failed to write file ${filePath} in container (exit code ${info2.ExitCode})`);
|
|
1526
|
-
}
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
|
-
const tempPath = `/tmp/b64_${Date.now()}.tmp`;
|
|
1530
|
-
const chunkSize = 8000;
|
|
1531
|
-
for (let i = 0;i < b64.length; i += chunkSize) {
|
|
1532
|
-
const chunk = b64.slice(i, i + chunkSize);
|
|
1533
|
-
const exec = await container.exec({
|
|
1534
|
-
Cmd: ["sh", "-c", `printf '%s' '${chunk}' >> ${tempPath}`],
|
|
1535
|
-
User: "sandbox"
|
|
1536
|
-
});
|
|
1537
|
-
await exec.start({ Detach: true });
|
|
1538
|
-
await exec.inspect();
|
|
1539
|
-
}
|
|
1540
|
-
const decodeExec = await container.exec({
|
|
1541
|
-
Cmd: ["sh", "-c", `base64 -d ${tempPath} > ${filePath} && rm ${tempPath}`],
|
|
1542
|
-
User: "sandbox"
|
|
1543
|
-
});
|
|
1544
|
-
await decodeExec.start({ Detach: true });
|
|
1545
|
-
let info = await decodeExec.inspect();
|
|
1546
|
-
while (info.Running) {
|
|
1547
|
-
await new Promise((r) => setTimeout(r, 5));
|
|
1548
|
-
info = await decodeExec.inspect();
|
|
1549
|
-
}
|
|
1550
|
-
if (info.ExitCode !== 0) {
|
|
1551
|
-
throw new Error(`Failed to write file ${filePath} in container (exit code ${info.ExitCode})`);
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
async function readFileViaExec(container, filePath) {
|
|
1555
|
-
const exec = await container.exec({
|
|
1556
|
-
Cmd: ["base64", filePath],
|
|
1557
|
-
AttachStdout: true,
|
|
1558
|
-
AttachStderr: true,
|
|
1559
|
-
User: "sandbox"
|
|
1560
|
-
});
|
|
1561
|
-
const stream = await exec.start({ Tty: false });
|
|
1562
|
-
const chunks = [];
|
|
1563
|
-
const stderrChunks = [];
|
|
1564
|
-
const stdoutStream = new PassThrough;
|
|
1565
|
-
const stderrStream = new PassThrough;
|
|
1566
|
-
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1567
|
-
stdoutStream.on("data", (chunk) => chunks.push(chunk));
|
|
1568
|
-
stderrStream.on("data", (chunk) => stderrChunks.push(chunk));
|
|
1569
|
-
await new Promise((resolve2, reject) => {
|
|
1570
|
-
stream.on("end", resolve2);
|
|
1571
|
-
stream.on("error", reject);
|
|
1572
|
-
});
|
|
1573
|
-
const inspectResult = await exec.inspect();
|
|
1574
|
-
if (inspectResult.ExitCode !== 0) {
|
|
1575
|
-
const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
|
|
1576
|
-
throw new Error(`Failed to read file ${filePath} in container: ${stderr} (exit code ${inspectResult.ExitCode})`);
|
|
1577
|
-
}
|
|
1578
|
-
const b64Output = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1579
|
-
return Buffer.from(b64Output, "base64");
|
|
1580
|
-
}
|
|
1581
|
-
var SANDBOX_WORKDIR = "/sandbox";
|
|
1582
|
-
var MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
1583
|
-
var PROXY_PORT = 8118;
|
|
1584
|
-
var PROXY_STARTUP_TIMEOUT_MS = 5000;
|
|
1585
|
-
var PROXY_POLL_INTERVAL_MS = 100;
|
|
1586
|
-
async function startProxy(container, networkFilter) {
|
|
1587
|
-
const envParts = [];
|
|
1588
|
-
if (networkFilter) {
|
|
1589
|
-
envParts.push(`ISOL8_WHITELIST='${JSON.stringify(networkFilter.whitelist)}'`);
|
|
1590
|
-
envParts.push(`ISOL8_BLACKLIST='${JSON.stringify(networkFilter.blacklist)}'`);
|
|
1591
|
-
}
|
|
1592
|
-
const envPrefix = envParts.length > 0 ? `${envParts.join(" ")} ` : "";
|
|
1593
|
-
const startExec = await container.exec({
|
|
1594
|
-
Cmd: ["sh", "-c", `${envPrefix}bash /usr/local/bin/proxy.sh &`]
|
|
1595
|
-
});
|
|
1596
|
-
await startExec.start({ Detach: true });
|
|
1597
|
-
const deadline = Date.now() + PROXY_STARTUP_TIMEOUT_MS;
|
|
1598
|
-
while (Date.now() < deadline) {
|
|
1599
|
-
try {
|
|
1600
|
-
const checkExec = await container.exec({
|
|
1601
|
-
Cmd: ["sh", "-c", `nc -z 127.0.0.1 ${PROXY_PORT} 2>/dev/null`]
|
|
1602
|
-
});
|
|
1603
|
-
await checkExec.start({ Detach: true });
|
|
1604
|
-
let info = await checkExec.inspect();
|
|
1605
|
-
while (info.Running) {
|
|
1606
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
1607
|
-
info = await checkExec.inspect();
|
|
1608
|
-
}
|
|
1609
|
-
if (info.ExitCode === 0) {
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
} catch {}
|
|
1613
|
-
await new Promise((r) => setTimeout(r, PROXY_POLL_INTERVAL_MS));
|
|
1614
|
-
}
|
|
1615
|
-
throw new Error("Proxy failed to start within timeout");
|
|
1616
|
-
}
|
|
1617
|
-
async function setupIptables(container) {
|
|
1618
|
-
const rules = [
|
|
1619
|
-
"/usr/sbin/iptables -A OUTPUT -o lo -j ACCEPT",
|
|
1620
|
-
"/usr/sbin/iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT",
|
|
1621
|
-
`/usr/sbin/iptables -A OUTPUT -p tcp -d 127.0.0.1 --dport ${PROXY_PORT} -m owner --uid-owner 100 -j ACCEPT`,
|
|
1622
|
-
"/usr/sbin/iptables -A OUTPUT -m owner --uid-owner 100 -j DROP"
|
|
1623
|
-
].join(" && ");
|
|
1624
|
-
const exec = await container.exec({
|
|
1625
|
-
Cmd: ["sh", "-c", rules]
|
|
1626
|
-
});
|
|
1627
|
-
await exec.start({ Detach: true });
|
|
1628
|
-
let info = await exec.inspect();
|
|
1629
|
-
while (info.Running) {
|
|
1630
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
1631
|
-
info = await exec.inspect();
|
|
1632
|
-
}
|
|
1633
|
-
if (info.ExitCode !== 0) {
|
|
1634
|
-
throw new Error(`Failed to set up iptables rules (exit code ${info.ExitCode})`);
|
|
1635
|
-
}
|
|
1636
|
-
logger.debug("[Filtered] iptables rules applied — sandbox user restricted to proxy only");
|
|
1637
|
-
}
|
|
1638
|
-
function wrapWithTimeout(cmd, timeoutSec) {
|
|
1639
|
-
return ["timeout", "-s", "KILL", String(timeoutSec), ...cmd];
|
|
1640
|
-
}
|
|
1641
|
-
function getInstallCommand(runtime, packages) {
|
|
1642
|
-
switch (runtime) {
|
|
1643
|
-
case "python":
|
|
1644
|
-
return [
|
|
1645
|
-
"pip",
|
|
1646
|
-
"install",
|
|
1647
|
-
"--user",
|
|
1648
|
-
"--no-cache-dir",
|
|
1649
|
-
"--break-system-packages",
|
|
1650
|
-
"--disable-pip-version-check",
|
|
1651
|
-
"--retries",
|
|
1652
|
-
"0",
|
|
1653
|
-
"--timeout",
|
|
1654
|
-
"15",
|
|
1655
|
-
...packages
|
|
1656
|
-
];
|
|
1657
|
-
case "node":
|
|
1658
|
-
return ["npm", "install", "--prefix", "/sandbox", ...packages];
|
|
1659
|
-
case "bun":
|
|
1660
|
-
return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
|
|
1661
|
-
case "deno":
|
|
1662
|
-
return ["sh", "-c", packages.map((p) => `deno cache ${p}`).join(" && ")];
|
|
1663
|
-
case "bash":
|
|
1664
|
-
return ["apk", "add", "--no-cache", ...packages];
|
|
1665
|
-
default:
|
|
1666
|
-
throw new Error(`Unknown runtime for package install: ${runtime}`);
|
|
2050
|
+
function calculateNetworkStats(stats) {
|
|
2051
|
+
if (!stats.networks) {
|
|
2052
|
+
return { in: 0, out: 0 };
|
|
2053
|
+
}
|
|
2054
|
+
let rxBytes = 0;
|
|
2055
|
+
let txBytes = 0;
|
|
2056
|
+
for (const iface of Object.values(stats.networks)) {
|
|
2057
|
+
rxBytes += iface.rx_bytes;
|
|
2058
|
+
txBytes += iface.tx_bytes;
|
|
1667
2059
|
}
|
|
2060
|
+
return { in: rxBytes, out: txBytes };
|
|
1668
2061
|
}
|
|
1669
|
-
async function
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
logger.debug(`Installing packages: ${JSON.stringify(cmd)}`);
|
|
1673
|
-
const env = [
|
|
1674
|
-
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
|
|
1675
|
-
];
|
|
1676
|
-
if (runtime === "python") {
|
|
1677
|
-
env.push("PYTHONUSERBASE=/sandbox/.local");
|
|
1678
|
-
} else if (runtime === "node") {
|
|
1679
|
-
env.push("NPM_CONFIG_PREFIX=/sandbox/.npm-global");
|
|
1680
|
-
env.push("NPM_CONFIG_CACHE=/sandbox/.npm-cache");
|
|
1681
|
-
env.push("npm_config_cache=/sandbox/.npm-cache");
|
|
1682
|
-
env.push("NPM_CONFIG_FETCH_RETRIES=0");
|
|
1683
|
-
env.push("npm_config_fetch_retries=0");
|
|
1684
|
-
env.push("NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=1000");
|
|
1685
|
-
env.push("npm_config_fetch_retry_mintimeout=1000");
|
|
1686
|
-
env.push("NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=2000");
|
|
1687
|
-
env.push("npm_config_fetch_retry_maxtimeout=2000");
|
|
1688
|
-
} else if (runtime === "bun") {
|
|
1689
|
-
env.push("BUN_INSTALL_GLOBAL_DIR=/sandbox/.bun-global");
|
|
1690
|
-
env.push("BUN_INSTALL_CACHE_DIR=/sandbox/.bun-cache");
|
|
1691
|
-
env.push("BUN_INSTALL_BIN=/sandbox/.bun-global/bin");
|
|
1692
|
-
} else if (runtime === "deno") {
|
|
1693
|
-
env.push("DENO_DIR=/sandbox/.deno");
|
|
1694
|
-
}
|
|
1695
|
-
const exec = await container.exec({
|
|
1696
|
-
Cmd: cmd,
|
|
1697
|
-
AttachStdout: true,
|
|
1698
|
-
AttachStderr: true,
|
|
1699
|
-
Env: env,
|
|
1700
|
-
User: runtime === "bash" ? "root" : "sandbox"
|
|
1701
|
-
});
|
|
1702
|
-
const stream = await exec.start({ Detach: false, Tty: false });
|
|
1703
|
-
return new Promise((resolve2, reject) => {
|
|
1704
|
-
let stderr = "";
|
|
1705
|
-
const stdoutStream = new PassThrough;
|
|
1706
|
-
const stderrStream = new PassThrough;
|
|
1707
|
-
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1708
|
-
stderrStream.on("data", (chunk) => {
|
|
1709
|
-
const text = chunk.toString();
|
|
1710
|
-
stderr += text;
|
|
1711
|
-
logger.debug(`[install:${runtime}:stderr] ${text.trimEnd()}`);
|
|
1712
|
-
});
|
|
1713
|
-
stdoutStream.on("data", (chunk) => {
|
|
1714
|
-
const text = chunk.toString();
|
|
1715
|
-
logger.debug(`[install:${runtime}:stdout] ${text.trimEnd()}`);
|
|
1716
|
-
});
|
|
1717
|
-
stream.on("end", async () => {
|
|
1718
|
-
try {
|
|
1719
|
-
const info = await exec.inspect();
|
|
1720
|
-
if (info.ExitCode !== 0) {
|
|
1721
|
-
reject(new Error(`Package install failed (exit code ${info.ExitCode}): ${stderr}`));
|
|
1722
|
-
} else {
|
|
1723
|
-
resolve2();
|
|
1724
|
-
}
|
|
1725
|
-
} catch (err) {
|
|
1726
|
-
reject(err);
|
|
1727
|
-
}
|
|
1728
|
-
});
|
|
1729
|
-
stream.on("error", reject);
|
|
2062
|
+
async function getContainerStats(container) {
|
|
2063
|
+
const stats = await container.stats({
|
|
2064
|
+
stream: false
|
|
1730
2065
|
});
|
|
2066
|
+
const cpuPercent = calculateCPUPercent(stats);
|
|
2067
|
+
const memoryBytes = stats.memory_stats.usage;
|
|
2068
|
+
const network = calculateNetworkStats(stats);
|
|
2069
|
+
return {
|
|
2070
|
+
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
|
2071
|
+
memoryMB: Math.round(memoryBytes / (1024 * 1024)),
|
|
2072
|
+
networkBytesIn: network.in,
|
|
2073
|
+
networkBytesOut: network.out
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
function calculateResourceDelta(before, after) {
|
|
2077
|
+
return {
|
|
2078
|
+
cpuPercent: after.cpuPercent,
|
|
2079
|
+
memoryMB: after.memoryMB,
|
|
2080
|
+
networkBytesIn: after.networkBytesIn - before.networkBytesIn,
|
|
2081
|
+
networkBytesOut: after.networkBytesOut - before.networkBytesOut
|
|
2082
|
+
};
|
|
1731
2083
|
}
|
|
1732
2084
|
|
|
2085
|
+
// src/engine/docker.ts
|
|
2086
|
+
var SANDBOX_WORKDIR = "/sandbox";
|
|
2087
|
+
var MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
2088
|
+
|
|
1733
2089
|
class DockerIsol8 {
|
|
1734
2090
|
docker;
|
|
1735
2091
|
mode;
|
|
@@ -1754,6 +2110,9 @@ class DockerIsol8 {
|
|
|
1754
2110
|
dependencies;
|
|
1755
2111
|
auditLogger;
|
|
1756
2112
|
remoteCodePolicy;
|
|
2113
|
+
networkManager;
|
|
2114
|
+
executionManager;
|
|
2115
|
+
volumeManager;
|
|
1757
2116
|
container = null;
|
|
1758
2117
|
persistentRuntime = null;
|
|
1759
2118
|
pool = null;
|
|
@@ -1813,6 +2172,18 @@ class DockerIsol8 {
|
|
|
1813
2172
|
if (options.audit) {
|
|
1814
2173
|
this.auditLogger = new AuditLogger(options.audit);
|
|
1815
2174
|
}
|
|
2175
|
+
this.networkManager = new NetworkManager({
|
|
2176
|
+
network: this.network,
|
|
2177
|
+
networkFilter: this.networkFilter
|
|
2178
|
+
});
|
|
2179
|
+
this.executionManager = new ExecutionManager({
|
|
2180
|
+
secrets: this.secrets,
|
|
2181
|
+
maxOutputSize: this.maxOutputSize
|
|
2182
|
+
});
|
|
2183
|
+
this.volumeManager = new VolumeManager({
|
|
2184
|
+
readonlyRootFs: this.readonlyRootFs,
|
|
2185
|
+
sandboxWorkdir: SANDBOX_WORKDIR
|
|
2186
|
+
});
|
|
1816
2187
|
if (options.debug) {
|
|
1817
2188
|
logger.setDebug(true);
|
|
1818
2189
|
}
|
|
@@ -1991,27 +2362,13 @@ class DockerIsol8 {
|
|
|
1991
2362
|
if (!this.container) {
|
|
1992
2363
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
1993
2364
|
}
|
|
1994
|
-
|
|
1995
|
-
await writeFileViaExec(this.container, path, content);
|
|
1996
|
-
} else {
|
|
1997
|
-
const tar = createTarBuffer(path, content);
|
|
1998
|
-
await this.container.putArchive(tar, { path: "/" });
|
|
1999
|
-
}
|
|
2365
|
+
await this.volumeManager.putFile(this.container, path, content);
|
|
2000
2366
|
}
|
|
2001
2367
|
async getFile(path) {
|
|
2002
2368
|
if (!this.container) {
|
|
2003
2369
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
2004
2370
|
}
|
|
2005
|
-
|
|
2006
|
-
return readFileViaExec(this.container, path);
|
|
2007
|
-
}
|
|
2008
|
-
const stream = await this.container.getArchive({ path });
|
|
2009
|
-
const chunks = [];
|
|
2010
|
-
for await (const chunk of stream) {
|
|
2011
|
-
chunks.push(chunk);
|
|
2012
|
-
}
|
|
2013
|
-
const tarBuffer = Buffer.concat(chunks);
|
|
2014
|
-
return extractFromTar(tarBuffer, path);
|
|
2371
|
+
return this.volumeManager.getFile(this.container, path);
|
|
2015
2372
|
}
|
|
2016
2373
|
get containerId() {
|
|
2017
2374
|
return this.container?.id ?? null;
|
|
@@ -2027,26 +2384,24 @@ class DockerIsol8 {
|
|
|
2027
2384
|
Image: image,
|
|
2028
2385
|
Cmd: ["sleep", "infinity"],
|
|
2029
2386
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2030
|
-
Env: this.buildEnv(),
|
|
2387
|
+
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2031
2388
|
NetworkDisabled: this.network === "none",
|
|
2032
2389
|
HostConfig: this.buildHostConfig(),
|
|
2033
2390
|
StopTimeout: 2
|
|
2034
2391
|
});
|
|
2035
2392
|
try {
|
|
2036
2393
|
await container.start();
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
await setupIptables(container);
|
|
2040
|
-
}
|
|
2394
|
+
await this.networkManager.startProxy(container);
|
|
2395
|
+
await this.networkManager.setupIptables(container);
|
|
2041
2396
|
const ext = request.fileExtension ?? adapter.getFileExtension();
|
|
2042
2397
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2043
|
-
await writeFileViaExec(container, filePath, request.code);
|
|
2398
|
+
await this.volumeManager.writeFileViaExec(container, filePath, request.code);
|
|
2044
2399
|
if (request.installPackages?.length) {
|
|
2045
|
-
await installPackages(container, request.runtime, request.installPackages, timeoutMs);
|
|
2400
|
+
await this.executionManager.installPackages(container, request.runtime, request.installPackages, timeoutMs);
|
|
2046
2401
|
}
|
|
2047
2402
|
if (request.files) {
|
|
2048
2403
|
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
2049
|
-
await writeFileViaExec(container, fPath, fContent);
|
|
2404
|
+
await this.volumeManager.writeFileViaExec(container, fPath, fContent);
|
|
2050
2405
|
}
|
|
2051
2406
|
}
|
|
2052
2407
|
const rawCmd = adapter.getCommand(request.code, filePath);
|
|
@@ -2054,22 +2409,22 @@ class DockerIsol8 {
|
|
|
2054
2409
|
let cmd;
|
|
2055
2410
|
if (request.stdin) {
|
|
2056
2411
|
const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
|
|
2057
|
-
await writeFileViaExec(container, stdinPath, request.stdin);
|
|
2412
|
+
await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
|
|
2058
2413
|
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
2059
|
-
cmd = wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2414
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2060
2415
|
} else {
|
|
2061
|
-
cmd = wrapWithTimeout(rawCmd, timeoutSec);
|
|
2416
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2062
2417
|
}
|
|
2063
2418
|
const exec = await container.exec({
|
|
2064
2419
|
Cmd: cmd,
|
|
2065
|
-
Env: this.buildEnv(request.env),
|
|
2420
|
+
Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2066
2421
|
AttachStdout: true,
|
|
2067
2422
|
AttachStderr: true,
|
|
2068
2423
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2069
2424
|
User: "sandbox"
|
|
2070
2425
|
});
|
|
2071
2426
|
const execStream = await exec.start({ Tty: false });
|
|
2072
|
-
yield* this.streamExecOutput(execStream, exec, container, timeoutMs);
|
|
2427
|
+
yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
|
|
2073
2428
|
} finally {
|
|
2074
2429
|
if (this.persist) {
|
|
2075
2430
|
logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
|
|
@@ -2144,7 +2499,7 @@ class DockerIsol8 {
|
|
|
2144
2499
|
createOptions: {
|
|
2145
2500
|
Cmd: ["sleep", "infinity"],
|
|
2146
2501
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2147
|
-
Env: this.buildEnv(),
|
|
2502
|
+
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2148
2503
|
NetworkDisabled: this.network === "none",
|
|
2149
2504
|
HostConfig: this.buildHostConfig(),
|
|
2150
2505
|
StopTimeout: 2
|
|
@@ -2168,10 +2523,8 @@ class DockerIsol8 {
|
|
|
2168
2523
|
}
|
|
2169
2524
|
}
|
|
2170
2525
|
try {
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
await setupIptables(container);
|
|
2174
|
-
}
|
|
2526
|
+
await this.networkManager.startProxy(container);
|
|
2527
|
+
await this.networkManager.setupIptables(container);
|
|
2175
2528
|
const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
|
|
2176
2529
|
let rawCmd;
|
|
2177
2530
|
if (canUseInline) {
|
|
@@ -2180,36 +2533,36 @@ class DockerIsol8 {
|
|
|
2180
2533
|
} catch {
|
|
2181
2534
|
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2182
2535
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2183
|
-
await writeFileViaExec(container, filePath, req.code);
|
|
2536
|
+
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2184
2537
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
2185
2538
|
}
|
|
2186
2539
|
} else {
|
|
2187
2540
|
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2188
2541
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2189
|
-
await writeFileViaExec(container, filePath, req.code);
|
|
2542
|
+
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2190
2543
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
2191
2544
|
}
|
|
2192
2545
|
if (req.installPackages?.length) {
|
|
2193
|
-
await installPackages(container, req.runtime, req.installPackages, timeoutMs);
|
|
2546
|
+
await this.executionManager.installPackages(container, req.runtime, req.installPackages, timeoutMs);
|
|
2194
2547
|
}
|
|
2195
2548
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2196
2549
|
let cmd;
|
|
2197
2550
|
if (req.stdin) {
|
|
2198
2551
|
const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
|
|
2199
|
-
await writeFileViaExec(container, stdinPath, req.stdin);
|
|
2552
|
+
await this.volumeManager.writeFileViaExec(container, stdinPath, req.stdin);
|
|
2200
2553
|
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}' `).join("");
|
|
2201
|
-
cmd = wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2554
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2202
2555
|
} else {
|
|
2203
|
-
cmd = wrapWithTimeout(rawCmd, timeoutSec);
|
|
2556
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2204
2557
|
}
|
|
2205
2558
|
if (req.files) {
|
|
2206
2559
|
for (const [fPath, fContent] of Object.entries(req.files)) {
|
|
2207
|
-
await writeFileViaExec(container, fPath, fContent);
|
|
2560
|
+
await this.volumeManager.writeFileViaExec(container, fPath, fContent);
|
|
2208
2561
|
}
|
|
2209
2562
|
}
|
|
2210
2563
|
const exec = await container.exec({
|
|
2211
2564
|
Cmd: cmd,
|
|
2212
|
-
Env: this.buildEnv(req.env),
|
|
2565
|
+
Env: this.executionManager.buildEnv(req.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2213
2566
|
AttachStdout: true,
|
|
2214
2567
|
AttachStderr: true,
|
|
2215
2568
|
WorkingDir: SANDBOX_WORKDIR,
|
|
@@ -2217,7 +2570,7 @@ class DockerIsol8 {
|
|
|
2217
2570
|
});
|
|
2218
2571
|
const start = performance.now();
|
|
2219
2572
|
const execStream = await exec.start({ Tty: false });
|
|
2220
|
-
const { stdout, stderr, truncated } = await this.collectExecOutput(execStream, container, timeoutMs);
|
|
2573
|
+
const { stdout, stderr, truncated } = await this.executionManager.collectExecOutput(execStream, container, timeoutMs);
|
|
2221
2574
|
const durationMs = Math.round(performance.now() - start);
|
|
2222
2575
|
const inspectResult = await exec.inspect();
|
|
2223
2576
|
let resourceUsage;
|
|
@@ -2241,8 +2594,8 @@ class DockerIsol8 {
|
|
|
2241
2594
|
}
|
|
2242
2595
|
}
|
|
2243
2596
|
const result = {
|
|
2244
|
-
stdout: this.postProcessOutput(stdout, truncated),
|
|
2245
|
-
stderr: this.postProcessOutput(stderr, false),
|
|
2597
|
+
stdout: this.executionManager.postProcessOutput(stdout, truncated),
|
|
2598
|
+
stderr: this.executionManager.postProcessOutput(stderr, false),
|
|
2246
2599
|
exitCode: inspectResult.ExitCode ?? 1,
|
|
2247
2600
|
durationMs,
|
|
2248
2601
|
truncated,
|
|
@@ -2252,7 +2605,7 @@ class DockerIsol8 {
|
|
|
2252
2605
|
containerId: container.id,
|
|
2253
2606
|
...resourceUsage ? { resourceUsage } : {},
|
|
2254
2607
|
...networkLogs ? { networkLogs } : {},
|
|
2255
|
-
...req.outputPaths ? { files: await this.retrieveFiles(container, req.outputPaths) } : {}
|
|
2608
|
+
...req.outputPaths ? { files: await this.volumeManager.retrieveFiles(container, req.outputPaths) } : {}
|
|
2256
2609
|
};
|
|
2257
2610
|
if (this.auditLogger) {
|
|
2258
2611
|
await this.recordAudit(req, result, startTime, container);
|
|
@@ -2279,37 +2632,27 @@ class DockerIsol8 {
|
|
|
2279
2632
|
}
|
|
2280
2633
|
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2281
2634
|
const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
|
|
2282
|
-
|
|
2283
|
-
await writeFileViaExec(this.container, filePath, req.code);
|
|
2284
|
-
} else {
|
|
2285
|
-
const tar = createTarBuffer(filePath, req.code);
|
|
2286
|
-
await this.container.putArchive(tar, { path: "/" });
|
|
2287
|
-
}
|
|
2635
|
+
await this.volumeManager.putFile(this.container, filePath, req.code);
|
|
2288
2636
|
if (req.files) {
|
|
2289
2637
|
for (const [fPath, fContent] of Object.entries(req.files)) {
|
|
2290
|
-
|
|
2291
|
-
await writeFileViaExec(this.container, fPath, fContent);
|
|
2292
|
-
} else {
|
|
2293
|
-
const tar = createTarBuffer(fPath, fContent);
|
|
2294
|
-
await this.container.putArchive(tar, { path: "/" });
|
|
2295
|
-
}
|
|
2638
|
+
await this.volumeManager.putFile(this.container, fPath, fContent);
|
|
2296
2639
|
}
|
|
2297
2640
|
}
|
|
2298
2641
|
const rawCmd = adapter.getCommand(req.code, filePath);
|
|
2299
2642
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2300
2643
|
if (req.installPackages?.length) {
|
|
2301
|
-
await installPackages(this.container, req.runtime, req.installPackages, timeoutMs);
|
|
2644
|
+
await this.executionManager.installPackages(this.container, req.runtime, req.installPackages, timeoutMs);
|
|
2302
2645
|
}
|
|
2303
2646
|
let cmd;
|
|
2304
2647
|
if (req.stdin) {
|
|
2305
2648
|
const stdinPath = `${SANDBOX_WORKDIR}/_stdin_${Date.now()}`;
|
|
2306
|
-
await writeFileViaExec(this.container, stdinPath, req.stdin);
|
|
2649
|
+
await this.volumeManager.writeFileViaExec(this.container, stdinPath, req.stdin);
|
|
2307
2650
|
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}' `).join("");
|
|
2308
|
-
cmd = wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2651
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2309
2652
|
} else {
|
|
2310
|
-
cmd = wrapWithTimeout(rawCmd, timeoutSec);
|
|
2653
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2311
2654
|
}
|
|
2312
|
-
const execEnv = this.buildEnv(req.env);
|
|
2655
|
+
const execEnv = this.executionManager.buildEnv(req.env, this.networkManager.proxyPort, this.network, this.networkFilter);
|
|
2313
2656
|
const exec = await this.container.exec({
|
|
2314
2657
|
Cmd: cmd,
|
|
2315
2658
|
Env: execEnv,
|
|
@@ -2320,7 +2663,7 @@ class DockerIsol8 {
|
|
|
2320
2663
|
});
|
|
2321
2664
|
const start = performance.now();
|
|
2322
2665
|
const execStream = await exec.start({ Tty: false });
|
|
2323
|
-
const { stdout, stderr, truncated } = await this.collectExecOutput(execStream, this.container, timeoutMs);
|
|
2666
|
+
const { stdout, stderr, truncated } = await this.executionManager.collectExecOutput(execStream, this.container, timeoutMs);
|
|
2324
2667
|
const durationMs = Math.round(performance.now() - start);
|
|
2325
2668
|
const inspectResult = await exec.inspect();
|
|
2326
2669
|
let resourceUsage;
|
|
@@ -2349,8 +2692,8 @@ class DockerIsol8 {
|
|
|
2349
2692
|
}
|
|
2350
2693
|
}
|
|
2351
2694
|
const result = {
|
|
2352
|
-
stdout: this.postProcessOutput(stdout, truncated),
|
|
2353
|
-
stderr: this.postProcessOutput(stderr, false),
|
|
2695
|
+
stdout: this.executionManager.postProcessOutput(stdout, truncated),
|
|
2696
|
+
stderr: this.executionManager.postProcessOutput(stderr, false),
|
|
2354
2697
|
exitCode: inspectResult.ExitCode ?? 1,
|
|
2355
2698
|
durationMs,
|
|
2356
2699
|
truncated,
|
|
@@ -2368,22 +2711,7 @@ class DockerIsol8 {
|
|
|
2368
2711
|
return result;
|
|
2369
2712
|
}
|
|
2370
2713
|
async retrieveFiles(container, paths) {
|
|
2371
|
-
|
|
2372
|
-
for (const p of paths) {
|
|
2373
|
-
try {
|
|
2374
|
-
const buf = this.readonlyRootFs ? await readFileViaExec(container, p) : await this.getFileFromContainer(container, p);
|
|
2375
|
-
files[p] = buf.toString("base64");
|
|
2376
|
-
} catch {}
|
|
2377
|
-
}
|
|
2378
|
-
return files;
|
|
2379
|
-
}
|
|
2380
|
-
async getFileFromContainer(container, path) {
|
|
2381
|
-
const stream = await container.getArchive({ path });
|
|
2382
|
-
const chunks = [];
|
|
2383
|
-
for await (const chunk of stream) {
|
|
2384
|
-
chunks.push(chunk);
|
|
2385
|
-
}
|
|
2386
|
-
return extractFromTar(Buffer.concat(chunks), path);
|
|
2714
|
+
return this.volumeManager.retrieveFiles(container, paths);
|
|
2387
2715
|
}
|
|
2388
2716
|
async startPersistentContainer(adapter) {
|
|
2389
2717
|
const image = await this.resolveImage(adapter);
|
|
@@ -2391,7 +2719,7 @@ class DockerIsol8 {
|
|
|
2391
2719
|
Image: image,
|
|
2392
2720
|
Cmd: ["sleep", "infinity"],
|
|
2393
2721
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2394
|
-
Env: this.buildEnv(),
|
|
2722
|
+
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2395
2723
|
NetworkDisabled: this.network === "none",
|
|
2396
2724
|
HostConfig: this.buildHostConfig(),
|
|
2397
2725
|
StopTimeout: 2,
|
|
@@ -2401,10 +2729,8 @@ class DockerIsol8 {
|
|
|
2401
2729
|
}
|
|
2402
2730
|
});
|
|
2403
2731
|
await this.container.start();
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
await setupIptables(this.container);
|
|
2407
|
-
}
|
|
2732
|
+
await this.networkManager.startProxy(this.container);
|
|
2733
|
+
await this.networkManager.setupIptables(this.container);
|
|
2408
2734
|
this.persistentRuntime = adapter;
|
|
2409
2735
|
}
|
|
2410
2736
|
getAdapter(runtime) {
|
|
@@ -2468,181 +2794,6 @@ class DockerIsol8 {
|
|
|
2468
2794
|
}
|
|
2469
2795
|
throw new Error("Embedded default seccomp profile is unavailable");
|
|
2470
2796
|
}
|
|
2471
|
-
buildEnv(extra) {
|
|
2472
|
-
const env = [
|
|
2473
|
-
"PYTHONUNBUFFERED=1",
|
|
2474
|
-
"PYTHONUSERBASE=/sandbox/.local",
|
|
2475
|
-
"NPM_CONFIG_PREFIX=/sandbox/.npm-global",
|
|
2476
|
-
"DENO_DIR=/sandbox/.deno",
|
|
2477
|
-
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin",
|
|
2478
|
-
"NODE_PATH=/usr/local/lib/node_modules:/sandbox/.npm-global/lib/node_modules:/sandbox/node_modules"
|
|
2479
|
-
];
|
|
2480
|
-
for (const [key, value] of Object.entries(this.secrets)) {
|
|
2481
|
-
env.push(`${key}=${value}`);
|
|
2482
|
-
}
|
|
2483
|
-
if (extra) {
|
|
2484
|
-
for (const [key, value] of Object.entries(extra)) {
|
|
2485
|
-
env.push(`${key}=${value}`);
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
if (this.network === "filtered") {
|
|
2489
|
-
if (this.networkFilter) {
|
|
2490
|
-
env.push(`ISOL8_WHITELIST=${JSON.stringify(this.networkFilter.whitelist)}`);
|
|
2491
|
-
env.push(`ISOL8_BLACKLIST=${JSON.stringify(this.networkFilter.blacklist)}`);
|
|
2492
|
-
}
|
|
2493
|
-
env.push(`HTTP_PROXY=http://127.0.0.1:${PROXY_PORT}`);
|
|
2494
|
-
env.push(`HTTPS_PROXY=http://127.0.0.1:${PROXY_PORT}`);
|
|
2495
|
-
env.push(`http_proxy=http://127.0.0.1:${PROXY_PORT}`);
|
|
2496
|
-
env.push(`https_proxy=http://127.0.0.1:${PROXY_PORT}`);
|
|
2497
|
-
}
|
|
2498
|
-
return env;
|
|
2499
|
-
}
|
|
2500
|
-
async* streamExecOutput(stream, exec, container, timeoutMs) {
|
|
2501
|
-
const queue = [];
|
|
2502
|
-
let resolve2 = null;
|
|
2503
|
-
let done = false;
|
|
2504
|
-
const push = (event) => {
|
|
2505
|
-
queue.push(event);
|
|
2506
|
-
if (resolve2) {
|
|
2507
|
-
resolve2();
|
|
2508
|
-
resolve2 = null;
|
|
2509
|
-
}
|
|
2510
|
-
};
|
|
2511
|
-
const timer = setTimeout(() => {
|
|
2512
|
-
push({ type: "error", data: "EXECUTION TIMED OUT" });
|
|
2513
|
-
push({ type: "exit", data: "137" });
|
|
2514
|
-
done = true;
|
|
2515
|
-
}, timeoutMs);
|
|
2516
|
-
const stdoutStream = new PassThrough;
|
|
2517
|
-
const stderrStream = new PassThrough;
|
|
2518
|
-
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
2519
|
-
stdoutStream.on("data", (chunk) => {
|
|
2520
|
-
let text = chunk.toString("utf-8");
|
|
2521
|
-
if (Object.keys(this.secrets).length > 0) {
|
|
2522
|
-
text = maskSecrets(text, this.secrets);
|
|
2523
|
-
}
|
|
2524
|
-
push({ type: "stdout", data: text });
|
|
2525
|
-
});
|
|
2526
|
-
stderrStream.on("data", (chunk) => {
|
|
2527
|
-
let text = chunk.toString("utf-8");
|
|
2528
|
-
if (Object.keys(this.secrets).length > 0) {
|
|
2529
|
-
text = maskSecrets(text, this.secrets);
|
|
2530
|
-
}
|
|
2531
|
-
push({ type: "stderr", data: text });
|
|
2532
|
-
});
|
|
2533
|
-
stream.on("end", async () => {
|
|
2534
|
-
clearTimeout(timer);
|
|
2535
|
-
try {
|
|
2536
|
-
const info = await exec.inspect();
|
|
2537
|
-
push({ type: "exit", data: (info.ExitCode ?? 0).toString() });
|
|
2538
|
-
} catch {
|
|
2539
|
-
push({ type: "exit", data: "1" });
|
|
2540
|
-
}
|
|
2541
|
-
done = true;
|
|
2542
|
-
});
|
|
2543
|
-
stream.on("error", (err) => {
|
|
2544
|
-
clearTimeout(timer);
|
|
2545
|
-
push({ type: "error", data: err.message });
|
|
2546
|
-
push({ type: "exit", data: "1" });
|
|
2547
|
-
done = true;
|
|
2548
|
-
});
|
|
2549
|
-
while (!done || queue.length > 0) {
|
|
2550
|
-
if (queue.length > 0) {
|
|
2551
|
-
yield queue.shift();
|
|
2552
|
-
} else if (resolve2) {
|
|
2553
|
-
await new Promise((r) => {
|
|
2554
|
-
resolve2 = r;
|
|
2555
|
-
});
|
|
2556
|
-
} else {
|
|
2557
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
async collectExecOutput(stream, container, timeoutMs) {
|
|
2562
|
-
return new Promise((resolve2, reject) => {
|
|
2563
|
-
let stdout = "";
|
|
2564
|
-
let stderr = "";
|
|
2565
|
-
let truncated = false;
|
|
2566
|
-
let settled = false;
|
|
2567
|
-
let stdoutEnded = false;
|
|
2568
|
-
let stderrEnded = false;
|
|
2569
|
-
const timer = setTimeout(() => {
|
|
2570
|
-
if (settled) {
|
|
2571
|
-
return;
|
|
2572
|
-
}
|
|
2573
|
-
settled = true;
|
|
2574
|
-
if (stream.destroy) {
|
|
2575
|
-
stream.destroy();
|
|
2576
|
-
}
|
|
2577
|
-
resolve2({ stdout, stderr: `${stderr}
|
|
2578
|
-
--- EXECUTION TIMED OUT ---`, truncated });
|
|
2579
|
-
}, timeoutMs);
|
|
2580
|
-
const stdoutStream = new PassThrough;
|
|
2581
|
-
const stderrStream = new PassThrough;
|
|
2582
|
-
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
2583
|
-
stdoutStream.on("data", (chunk) => {
|
|
2584
|
-
stdout += chunk.toString("utf-8");
|
|
2585
|
-
if (stdout.length > this.maxOutputSize) {
|
|
2586
|
-
const result = truncateOutput(stdout, this.maxOutputSize);
|
|
2587
|
-
stdout = result.text;
|
|
2588
|
-
truncated = true;
|
|
2589
|
-
}
|
|
2590
|
-
});
|
|
2591
|
-
stderrStream.on("data", (chunk) => {
|
|
2592
|
-
stderr += chunk.toString("utf-8");
|
|
2593
|
-
if (stderr.length > this.maxOutputSize) {
|
|
2594
|
-
const result = truncateOutput(stderr, this.maxOutputSize);
|
|
2595
|
-
stderr = result.text;
|
|
2596
|
-
truncated = true;
|
|
2597
|
-
}
|
|
2598
|
-
});
|
|
2599
|
-
const checkDone = () => {
|
|
2600
|
-
if (settled) {
|
|
2601
|
-
return;
|
|
2602
|
-
}
|
|
2603
|
-
if (stdoutEnded && stderrEnded) {
|
|
2604
|
-
settled = true;
|
|
2605
|
-
clearTimeout(timer);
|
|
2606
|
-
resolve2({ stdout, stderr, truncated });
|
|
2607
|
-
}
|
|
2608
|
-
};
|
|
2609
|
-
stdoutStream.on("end", () => {
|
|
2610
|
-
stdoutEnded = true;
|
|
2611
|
-
checkDone();
|
|
2612
|
-
});
|
|
2613
|
-
stderrStream.on("end", () => {
|
|
2614
|
-
stderrEnded = true;
|
|
2615
|
-
checkDone();
|
|
2616
|
-
});
|
|
2617
|
-
stream.on("error", (err) => {
|
|
2618
|
-
if (settled) {
|
|
2619
|
-
return;
|
|
2620
|
-
}
|
|
2621
|
-
settled = true;
|
|
2622
|
-
clearTimeout(timer);
|
|
2623
|
-
reject(err);
|
|
2624
|
-
});
|
|
2625
|
-
stream.on("end", () => {
|
|
2626
|
-
if (settled) {
|
|
2627
|
-
return;
|
|
2628
|
-
}
|
|
2629
|
-
setTimeout(() => {
|
|
2630
|
-
if (!settled) {
|
|
2631
|
-
stdoutEnded = true;
|
|
2632
|
-
stderrEnded = true;
|
|
2633
|
-
checkDone();
|
|
2634
|
-
}
|
|
2635
|
-
}, 100);
|
|
2636
|
-
});
|
|
2637
|
-
});
|
|
2638
|
-
}
|
|
2639
|
-
postProcessOutput(output, _truncated) {
|
|
2640
|
-
let result = output;
|
|
2641
|
-
if (Object.keys(this.secrets).length > 0) {
|
|
2642
|
-
result = maskSecrets(result, this.secrets);
|
|
2643
|
-
}
|
|
2644
|
-
return result.trimEnd();
|
|
2645
|
-
}
|
|
2646
2797
|
static async cleanup(docker) {
|
|
2647
2798
|
const dockerInstance = docker ?? new Docker;
|
|
2648
2799
|
const containers = await dockerInstance.listContainers({ all: true });
|
|
@@ -2693,7 +2844,7 @@ init_logger();
|
|
|
2693
2844
|
// package.json
|
|
2694
2845
|
var package_default = {
|
|
2695
2846
|
name: "@isol8/core",
|
|
2696
|
-
version: "0.
|
|
2847
|
+
version: "0.16.0",
|
|
2697
2848
|
description: "Sandboxed code execution engine for AI agents and apps (Docker, runtime and network controls)",
|
|
2698
2849
|
author: "Illusion47586",
|
|
2699
2850
|
license: "MIT",
|
|
@@ -2721,7 +2872,7 @@ var package_default = {
|
|
|
2721
2872
|
"test:prod": "echo 'No production tests in core package'",
|
|
2722
2873
|
"lint:check": "ultracite check",
|
|
2723
2874
|
"lint:fix": "ultracite fix",
|
|
2724
|
-
schema: "ts-json-schema-generator --path src/types.ts --type Isol8UserConfig --tsconfig tsconfig.json -o schema/isol8.config.schema.json && ultracite fix schema/isol8.config.schema.json"
|
|
2875
|
+
schema: "ts-json-schema-generator --path src/types.ts --type Isol8UserConfig --tsconfig tsconfig.json --no-type-check -o schema/isol8.config.schema.json && ultracite fix schema/isol8.config.schema.json"
|
|
2725
2876
|
},
|
|
2726
2877
|
dependencies: {
|
|
2727
2878
|
dockerode: "^4.0.9",
|
|
@@ -2777,4 +2928,4 @@ export {
|
|
|
2777
2928
|
BunAdapter
|
|
2778
2929
|
};
|
|
2779
2930
|
|
|
2780
|
-
//# debugId=
|
|
2931
|
+
//# debugId=A775CBFF7103831D64756E2164756E21
|