@isol8/core 0.14.6 → 0.15.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/README.md +16 -0
- package/dist/client/remote.d.ts +14 -1
- package/dist/client/remote.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 +791 -641
- package/dist/index.js.map +9 -6
- package/dist/types.d.ts +25 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -2
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({
|
|
@@ -848,7 +944,6 @@ init_runtime();
|
|
|
848
944
|
init_logger();
|
|
849
945
|
import { randomUUID } from "node:crypto";
|
|
850
946
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
851
|
-
import { PassThrough } from "node:stream";
|
|
852
947
|
import Docker from "dockerode";
|
|
853
948
|
|
|
854
949
|
// src/engine/audit.ts
|
|
@@ -1228,140 +1323,612 @@ var EMBEDDED_DEFAULT_SECCOMP_PROFILE = JSON.stringify({
|
|
|
1228
1323
|
// src/engine/docker.ts
|
|
1229
1324
|
init_image_builder();
|
|
1230
1325
|
|
|
1231
|
-
// src/engine/
|
|
1326
|
+
// src/engine/managers/execution-manager.ts
|
|
1232
1327
|
init_logger();
|
|
1328
|
+
import { PassThrough } from "node:stream";
|
|
1233
1329
|
|
|
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;
|
|
1330
|
+
class ExecutionManager {
|
|
1331
|
+
secrets;
|
|
1332
|
+
maxOutputSize;
|
|
1246
1333
|
constructor(options) {
|
|
1247
|
-
this.
|
|
1248
|
-
this.
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1334
|
+
this.secrets = options.secrets;
|
|
1335
|
+
this.maxOutputSize = options.maxOutputSize;
|
|
1336
|
+
}
|
|
1337
|
+
wrapWithTimeout(cmd, timeoutSec) {
|
|
1338
|
+
return ["timeout", "-s", "KILL", String(timeoutSec), ...cmd];
|
|
1339
|
+
}
|
|
1340
|
+
getInstallCommand(runtime, packages) {
|
|
1341
|
+
switch (runtime) {
|
|
1342
|
+
case "python":
|
|
1343
|
+
return [
|
|
1344
|
+
"pip",
|
|
1345
|
+
"install",
|
|
1346
|
+
"--user",
|
|
1347
|
+
"--no-cache-dir",
|
|
1348
|
+
"--break-system-packages",
|
|
1349
|
+
"--disable-pip-version-check",
|
|
1350
|
+
"--retries",
|
|
1351
|
+
"0",
|
|
1352
|
+
"--timeout",
|
|
1353
|
+
"15",
|
|
1354
|
+
...packages
|
|
1355
|
+
];
|
|
1356
|
+
case "node":
|
|
1357
|
+
return ["npm", "install", "--prefix", "/sandbox", ...packages];
|
|
1358
|
+
case "bun":
|
|
1359
|
+
return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
|
|
1360
|
+
case "deno":
|
|
1361
|
+
return ["sh", "-c", packages.map((p) => `deno cache ${p}`).join(" && ")];
|
|
1362
|
+
case "bash":
|
|
1363
|
+
return ["apk", "add", "--no-cache", ...packages];
|
|
1364
|
+
default:
|
|
1365
|
+
throw new Error(`Unknown runtime for package install: ${runtime}`);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
async installPackages(container, runtime, packages, timeoutMs) {
|
|
1369
|
+
const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
1370
|
+
const cmd = this.wrapWithTimeout(this.getInstallCommand(runtime, packages), timeoutSec);
|
|
1371
|
+
logger.debug(`Installing packages: ${JSON.stringify(cmd)}`);
|
|
1372
|
+
const env = [
|
|
1373
|
+
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
|
|
1374
|
+
];
|
|
1375
|
+
if (runtime === "python") {
|
|
1376
|
+
env.push("PYTHONUSERBASE=/sandbox/.local");
|
|
1377
|
+
} else if (runtime === "node") {
|
|
1378
|
+
env.push("NPM_CONFIG_PREFIX=/sandbox/.npm-global");
|
|
1379
|
+
env.push("NPM_CONFIG_CACHE=/sandbox/.npm-cache");
|
|
1380
|
+
env.push("npm_config_cache=/sandbox/.npm-cache");
|
|
1381
|
+
env.push("NPM_CONFIG_FETCH_RETRIES=0");
|
|
1382
|
+
env.push("npm_config_fetch_retries=0");
|
|
1383
|
+
env.push("NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=1000");
|
|
1384
|
+
env.push("npm_config_fetch_retry_mintimeout=1000");
|
|
1385
|
+
env.push("NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=2000");
|
|
1386
|
+
env.push("npm_config_fetch_retry_maxtimeout=2000");
|
|
1387
|
+
} else if (runtime === "bun") {
|
|
1388
|
+
env.push("BUN_INSTALL_GLOBAL_DIR=/sandbox/.bun-global");
|
|
1389
|
+
env.push("BUN_INSTALL_CACHE_DIR=/sandbox/.bun-cache");
|
|
1390
|
+
env.push("BUN_INSTALL_BIN=/sandbox/.bun-global/bin");
|
|
1391
|
+
} else if (runtime === "deno") {
|
|
1392
|
+
env.push("DENO_DIR=/sandbox/.deno");
|
|
1264
1393
|
}
|
|
1394
|
+
const exec = await container.exec({
|
|
1395
|
+
Cmd: cmd,
|
|
1396
|
+
AttachStdout: true,
|
|
1397
|
+
AttachStderr: true,
|
|
1398
|
+
Env: env,
|
|
1399
|
+
User: runtime === "bash" ? "root" : "sandbox"
|
|
1400
|
+
});
|
|
1401
|
+
const stream = await exec.start({ Detach: false, Tty: false });
|
|
1402
|
+
return new Promise((resolve2, reject) => {
|
|
1403
|
+
let stderr = "";
|
|
1404
|
+
const stdoutStream = new PassThrough;
|
|
1405
|
+
const stderrStream = new PassThrough;
|
|
1406
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1407
|
+
stderrStream.on("data", (chunk) => {
|
|
1408
|
+
const text = chunk.toString();
|
|
1409
|
+
stderr += text;
|
|
1410
|
+
logger.debug(`[install:${runtime}:stderr] ${text.trimEnd()}`);
|
|
1411
|
+
});
|
|
1412
|
+
stdoutStream.on("data", (chunk) => {
|
|
1413
|
+
const text = chunk.toString();
|
|
1414
|
+
logger.debug(`[install:${runtime}:stdout] ${text.trimEnd()}`);
|
|
1415
|
+
});
|
|
1416
|
+
stream.on("end", async () => {
|
|
1417
|
+
try {
|
|
1418
|
+
const info = await exec.inspect();
|
|
1419
|
+
if (info.ExitCode !== 0) {
|
|
1420
|
+
reject(new Error(`Package install failed (exit code ${info.ExitCode}): ${stderr}`));
|
|
1421
|
+
} else {
|
|
1422
|
+
resolve2();
|
|
1423
|
+
}
|
|
1424
|
+
} catch (err) {
|
|
1425
|
+
reject(err);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
stream.on("error", reject);
|
|
1429
|
+
});
|
|
1265
1430
|
}
|
|
1266
|
-
async
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1431
|
+
async* streamExecOutput(stream, exec, container, timeoutMs) {
|
|
1432
|
+
const queue = [];
|
|
1433
|
+
let resolve2 = null;
|
|
1434
|
+
let done = false;
|
|
1435
|
+
const push = (event) => {
|
|
1436
|
+
queue.push(event);
|
|
1437
|
+
if (resolve2) {
|
|
1438
|
+
resolve2();
|
|
1439
|
+
resolve2 = null;
|
|
1274
1440
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1441
|
+
};
|
|
1442
|
+
const timer = setTimeout(() => {
|
|
1443
|
+
push({ type: "error", data: "EXECUTION TIMED OUT" });
|
|
1444
|
+
push({ type: "exit", data: "137" });
|
|
1445
|
+
done = true;
|
|
1446
|
+
}, timeoutMs);
|
|
1447
|
+
const stdoutStream = new PassThrough;
|
|
1448
|
+
const stderrStream = new PassThrough;
|
|
1449
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1450
|
+
stdoutStream.on("data", (chunk) => {
|
|
1451
|
+
let text = chunk.toString("utf-8");
|
|
1452
|
+
if (Object.keys(this.secrets).length > 0) {
|
|
1453
|
+
text = maskSecrets(text, this.secrets);
|
|
1284
1454
|
}
|
|
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;
|
|
1455
|
+
push({ type: "stdout", data: text });
|
|
1456
|
+
});
|
|
1457
|
+
stderrStream.on("data", (chunk) => {
|
|
1458
|
+
let text = chunk.toString("utf-8");
|
|
1459
|
+
if (Object.keys(this.secrets).length > 0) {
|
|
1460
|
+
text = maskSecrets(text, this.secrets);
|
|
1306
1461
|
}
|
|
1307
|
-
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1462
|
+
push({ type: "stderr", data: text });
|
|
1463
|
+
});
|
|
1464
|
+
stream.on("end", async () => {
|
|
1465
|
+
clearTimeout(timer);
|
|
1466
|
+
try {
|
|
1467
|
+
const info = await exec.inspect();
|
|
1468
|
+
push({ type: "exit", data: (info.ExitCode ?? 0).toString() });
|
|
1469
|
+
} catch {
|
|
1470
|
+
push({ type: "exit", data: "1" });
|
|
1312
1471
|
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1472
|
+
done = true;
|
|
1473
|
+
});
|
|
1474
|
+
stream.on("error", (err) => {
|
|
1475
|
+
clearTimeout(timer);
|
|
1476
|
+
push({ type: "error", data: err.message });
|
|
1477
|
+
push({ type: "exit", data: "1" });
|
|
1478
|
+
done = true;
|
|
1479
|
+
});
|
|
1480
|
+
while (!done || queue.length > 0) {
|
|
1481
|
+
if (queue.length > 0) {
|
|
1482
|
+
yield queue.shift();
|
|
1483
|
+
} else if (resolve2) {
|
|
1484
|
+
await new Promise((r) => {
|
|
1485
|
+
resolve2 = r;
|
|
1486
|
+
});
|
|
1487
|
+
} else {
|
|
1488
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
1315
1489
|
}
|
|
1316
|
-
pool.clean.push({ container, createdAt: Date.now() });
|
|
1317
1490
|
}
|
|
1318
1491
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1492
|
+
async collectExecOutput(stream, container, timeoutMs) {
|
|
1493
|
+
return new Promise((resolve2, reject) => {
|
|
1494
|
+
let stdout = "";
|
|
1495
|
+
let stderr = "";
|
|
1496
|
+
let truncated = false;
|
|
1497
|
+
let settled = false;
|
|
1498
|
+
let stdoutEnded = false;
|
|
1499
|
+
let stderrEnded = false;
|
|
1500
|
+
const timer = setTimeout(() => {
|
|
1501
|
+
if (settled) {
|
|
1502
|
+
return;
|
|
1332
1503
|
}
|
|
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
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1504
|
+
settled = true;
|
|
1505
|
+
if (stream.destroy) {
|
|
1506
|
+
stream.destroy();
|
|
1507
|
+
}
|
|
1508
|
+
resolve2({ stdout, stderr: `${stderr}
|
|
1509
|
+
--- EXECUTION TIMED OUT ---`, truncated });
|
|
1510
|
+
}, timeoutMs);
|
|
1511
|
+
const stdoutStream = new PassThrough;
|
|
1512
|
+
const stderrStream = new PassThrough;
|
|
1513
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1514
|
+
stdoutStream.on("data", (chunk) => {
|
|
1515
|
+
stdout += chunk.toString("utf-8");
|
|
1516
|
+
if (stdout.length > this.maxOutputSize) {
|
|
1517
|
+
const result = truncateOutput(stdout, this.maxOutputSize);
|
|
1518
|
+
stdout = result.text;
|
|
1519
|
+
truncated = true;
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
stderrStream.on("data", (chunk) => {
|
|
1523
|
+
stderr += chunk.toString("utf-8");
|
|
1524
|
+
if (stderr.length > this.maxOutputSize) {
|
|
1525
|
+
const result = truncateOutput(stderr, this.maxOutputSize);
|
|
1526
|
+
stderr = result.text;
|
|
1527
|
+
truncated = true;
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
const checkDone = () => {
|
|
1531
|
+
if (settled) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (stdoutEnded && stderrEnded) {
|
|
1535
|
+
settled = true;
|
|
1536
|
+
clearTimeout(timer);
|
|
1537
|
+
resolve2({ stdout, stderr, truncated });
|
|
1538
|
+
}
|
|
1539
|
+
};
|
|
1540
|
+
stdoutStream.on("end", () => {
|
|
1541
|
+
stdoutEnded = true;
|
|
1542
|
+
checkDone();
|
|
1543
|
+
});
|
|
1544
|
+
stderrStream.on("end", () => {
|
|
1545
|
+
stderrEnded = true;
|
|
1546
|
+
checkDone();
|
|
1547
|
+
});
|
|
1548
|
+
stream.on("error", (err) => {
|
|
1549
|
+
if (settled) {
|
|
1550
|
+
return;
|
|
1551
|
+
}
|
|
1552
|
+
settled = true;
|
|
1553
|
+
clearTimeout(timer);
|
|
1554
|
+
reject(err);
|
|
1555
|
+
});
|
|
1556
|
+
stream.on("end", () => {
|
|
1557
|
+
if (settled) {
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
setTimeout(() => {
|
|
1561
|
+
if (!settled) {
|
|
1562
|
+
stdoutEnded = true;
|
|
1563
|
+
stderrEnded = true;
|
|
1564
|
+
checkDone();
|
|
1565
|
+
}
|
|
1566
|
+
}, 100);
|
|
1567
|
+
});
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
postProcessOutput(output, _truncated) {
|
|
1571
|
+
let result = output;
|
|
1572
|
+
if (Object.keys(this.secrets).length > 0) {
|
|
1573
|
+
result = maskSecrets(result, this.secrets);
|
|
1574
|
+
}
|
|
1575
|
+
return result.trimEnd();
|
|
1576
|
+
}
|
|
1577
|
+
buildEnv(extra, proxyPort, networkMode, networkFilter) {
|
|
1578
|
+
const env = [
|
|
1579
|
+
"PYTHONUNBUFFERED=1",
|
|
1580
|
+
"PYTHONUSERBASE=/sandbox/.local",
|
|
1581
|
+
"NPM_CONFIG_PREFIX=/sandbox/.npm-global",
|
|
1582
|
+
"DENO_DIR=/sandbox/.deno",
|
|
1583
|
+
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin",
|
|
1584
|
+
"NODE_PATH=/usr/local/lib/node_modules:/sandbox/.npm-global/lib/node_modules:/sandbox/node_modules"
|
|
1585
|
+
];
|
|
1586
|
+
for (const [key, value] of Object.entries(this.secrets)) {
|
|
1587
|
+
env.push(`${key}=${value}`);
|
|
1588
|
+
}
|
|
1589
|
+
if (extra) {
|
|
1590
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
1591
|
+
env.push(`${key}=${value}`);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
if (networkMode === "filtered") {
|
|
1595
|
+
if (networkFilter) {
|
|
1596
|
+
env.push(`ISOL8_WHITELIST=${JSON.stringify(networkFilter.whitelist)}`);
|
|
1597
|
+
env.push(`ISOL8_BLACKLIST=${JSON.stringify(networkFilter.blacklist)}`);
|
|
1598
|
+
}
|
|
1599
|
+
if (proxyPort) {
|
|
1600
|
+
env.push(`HTTP_PROXY=http://127.0.0.1:${proxyPort}`);
|
|
1601
|
+
env.push(`HTTPS_PROXY=http://127.0.0.1:${proxyPort}`);
|
|
1602
|
+
env.push(`http_proxy=http://127.0.0.1:${proxyPort}`);
|
|
1603
|
+
env.push(`https_proxy=http://127.0.0.1:${proxyPort}`);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return env;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
// src/engine/managers/network-manager.ts
|
|
1610
|
+
init_logger();
|
|
1611
|
+
var PROXY_PORT = 8118;
|
|
1612
|
+
var PROXY_STARTUP_TIMEOUT_MS = 5000;
|
|
1613
|
+
var PROXY_POLL_INTERVAL_MS = 100;
|
|
1614
|
+
|
|
1615
|
+
class NetworkManager {
|
|
1616
|
+
network;
|
|
1617
|
+
networkFilter;
|
|
1618
|
+
constructor(options) {
|
|
1619
|
+
this.network = options.network;
|
|
1620
|
+
this.networkFilter = options.networkFilter;
|
|
1621
|
+
}
|
|
1622
|
+
async startProxy(container) {
|
|
1623
|
+
if (this.network !== "filtered") {
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1626
|
+
const envParts = [];
|
|
1627
|
+
if (this.networkFilter) {
|
|
1628
|
+
envParts.push(`ISOL8_WHITELIST='${JSON.stringify(this.networkFilter.whitelist)}'`);
|
|
1629
|
+
envParts.push(`ISOL8_BLACKLIST='${JSON.stringify(this.networkFilter.blacklist)}'`);
|
|
1630
|
+
}
|
|
1631
|
+
const envPrefix = envParts.length > 0 ? `${envParts.join(" ")} ` : "";
|
|
1632
|
+
const startExec = await container.exec({
|
|
1633
|
+
Cmd: ["sh", "-c", `${envPrefix}bash /usr/local/bin/proxy.sh &`]
|
|
1634
|
+
});
|
|
1635
|
+
await startExec.start({ Detach: true });
|
|
1636
|
+
const deadline = Date.now() + PROXY_STARTUP_TIMEOUT_MS;
|
|
1637
|
+
while (Date.now() < deadline) {
|
|
1638
|
+
try {
|
|
1639
|
+
const checkExec = await container.exec({
|
|
1640
|
+
Cmd: ["sh", "-c", `nc -z 127.0.0.1 ${PROXY_PORT} 2>/dev/null`]
|
|
1641
|
+
});
|
|
1642
|
+
await checkExec.start({ Detach: true });
|
|
1643
|
+
let info = await checkExec.inspect();
|
|
1644
|
+
while (info.Running) {
|
|
1645
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1646
|
+
info = await checkExec.inspect();
|
|
1647
|
+
}
|
|
1648
|
+
if (info.ExitCode === 0) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
} catch {}
|
|
1652
|
+
await new Promise((r) => setTimeout(r, PROXY_POLL_INTERVAL_MS));
|
|
1653
|
+
}
|
|
1654
|
+
throw new Error("Proxy failed to start within timeout");
|
|
1655
|
+
}
|
|
1656
|
+
async setupIptables(container) {
|
|
1657
|
+
if (this.network !== "filtered") {
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
const rules = [
|
|
1661
|
+
"/usr/sbin/iptables -A OUTPUT -o lo -j ACCEPT",
|
|
1662
|
+
"/usr/sbin/iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT",
|
|
1663
|
+
`/usr/sbin/iptables -A OUTPUT -p tcp -d 127.0.0.1 --dport ${PROXY_PORT} -m owner --uid-owner 100 -j ACCEPT`,
|
|
1664
|
+
"/usr/sbin/iptables -A OUTPUT -m owner --uid-owner 100 -j DROP"
|
|
1665
|
+
].join(" && ");
|
|
1666
|
+
const exec = await container.exec({
|
|
1667
|
+
Cmd: ["sh", "-c", rules]
|
|
1668
|
+
});
|
|
1669
|
+
await exec.start({ Detach: true });
|
|
1670
|
+
let info = await exec.inspect();
|
|
1671
|
+
while (info.Running) {
|
|
1672
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1673
|
+
info = await exec.inspect();
|
|
1674
|
+
}
|
|
1675
|
+
if (info.ExitCode !== 0) {
|
|
1676
|
+
throw new Error(`Failed to set up iptables rules (exit code ${info.ExitCode})`);
|
|
1677
|
+
}
|
|
1678
|
+
logger.debug("[Filtered] iptables rules applied — sandbox user restricted to proxy only");
|
|
1679
|
+
}
|
|
1680
|
+
get proxyPort() {
|
|
1681
|
+
return PROXY_PORT;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
// src/engine/managers/volume-manager.ts
|
|
1685
|
+
import { PassThrough as PassThrough2 } from "node:stream";
|
|
1686
|
+
|
|
1687
|
+
class VolumeManager {
|
|
1688
|
+
readonlyRootFs;
|
|
1689
|
+
sandboxWorkdir;
|
|
1690
|
+
constructor(options) {
|
|
1691
|
+
this.readonlyRootFs = options.readonlyRootFs;
|
|
1692
|
+
this.sandboxWorkdir = options.sandboxWorkdir ?? "/sandbox";
|
|
1693
|
+
}
|
|
1694
|
+
async writeFileViaExec(container, filePath, content) {
|
|
1695
|
+
const data = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
1696
|
+
const b64 = data.toString("base64");
|
|
1697
|
+
if (b64.length < 20000) {
|
|
1698
|
+
const exec = await container.exec({
|
|
1699
|
+
Cmd: ["sh", "-c", `printf '%s' '${b64}' | base64 -d > ${filePath}`],
|
|
1700
|
+
User: "sandbox"
|
|
1701
|
+
});
|
|
1702
|
+
await exec.start({ Detach: true });
|
|
1703
|
+
let info2 = await exec.inspect();
|
|
1704
|
+
while (info2.Running) {
|
|
1705
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1706
|
+
info2 = await exec.inspect();
|
|
1707
|
+
}
|
|
1708
|
+
if (info2.ExitCode !== 0) {
|
|
1709
|
+
throw new Error(`Failed to write file ${filePath} in container (exit code ${info2.ExitCode})`);
|
|
1710
|
+
}
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
const tempPath = `/tmp/b64_${Date.now()}.tmp`;
|
|
1714
|
+
const chunkSize = 8000;
|
|
1715
|
+
for (let i = 0;i < b64.length; i += chunkSize) {
|
|
1716
|
+
const chunk = b64.slice(i, i + chunkSize);
|
|
1717
|
+
const exec = await container.exec({
|
|
1718
|
+
Cmd: ["sh", "-c", `printf '%s' '${chunk}' >> ${tempPath}`],
|
|
1719
|
+
User: "sandbox"
|
|
1720
|
+
});
|
|
1721
|
+
await exec.start({ Detach: true });
|
|
1722
|
+
await exec.inspect();
|
|
1723
|
+
}
|
|
1724
|
+
const decodeExec = await container.exec({
|
|
1725
|
+
Cmd: ["sh", "-c", `base64 -d ${tempPath} > ${filePath} && rm ${tempPath}`],
|
|
1726
|
+
User: "sandbox"
|
|
1727
|
+
});
|
|
1728
|
+
await decodeExec.start({ Detach: true });
|
|
1729
|
+
let info = await decodeExec.inspect();
|
|
1730
|
+
while (info.Running) {
|
|
1731
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1732
|
+
info = await decodeExec.inspect();
|
|
1733
|
+
}
|
|
1734
|
+
if (info.ExitCode !== 0) {
|
|
1735
|
+
throw new Error(`Failed to write file ${filePath} in container (exit code ${info.ExitCode})`);
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
async readFileViaExec(container, filePath) {
|
|
1739
|
+
const exec = await container.exec({
|
|
1740
|
+
Cmd: ["base64", filePath],
|
|
1741
|
+
AttachStdout: true,
|
|
1742
|
+
AttachStderr: true,
|
|
1743
|
+
User: "sandbox"
|
|
1744
|
+
});
|
|
1745
|
+
const stream = await exec.start({ Tty: false });
|
|
1746
|
+
const chunks = [];
|
|
1747
|
+
const stderrChunks = [];
|
|
1748
|
+
const stdoutStream = new PassThrough2;
|
|
1749
|
+
const stderrStream = new PassThrough2;
|
|
1750
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1751
|
+
stdoutStream.on("data", (chunk) => chunks.push(chunk));
|
|
1752
|
+
stderrStream.on("data", (chunk) => stderrChunks.push(chunk));
|
|
1753
|
+
await new Promise((resolve2, reject) => {
|
|
1754
|
+
stream.on("end", resolve2);
|
|
1755
|
+
stream.on("error", reject);
|
|
1756
|
+
});
|
|
1757
|
+
const inspectResult = await exec.inspect();
|
|
1758
|
+
if (inspectResult.ExitCode !== 0) {
|
|
1759
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
|
|
1760
|
+
throw new Error(`Failed to read file ${filePath} in container: ${stderr} (exit code ${inspectResult.ExitCode})`);
|
|
1761
|
+
}
|
|
1762
|
+
const b64Output = Buffer.concat(chunks).toString("utf-8").trim();
|
|
1763
|
+
return Buffer.from(b64Output, "base64");
|
|
1764
|
+
}
|
|
1765
|
+
async getFileFromContainer(container, path) {
|
|
1766
|
+
const stream = await container.getArchive({ path });
|
|
1767
|
+
const chunks = [];
|
|
1768
|
+
for await (const chunk of stream) {
|
|
1769
|
+
chunks.push(chunk);
|
|
1770
|
+
}
|
|
1771
|
+
return extractFromTar(Buffer.concat(chunks), path);
|
|
1772
|
+
}
|
|
1773
|
+
async retrieveFiles(container, paths) {
|
|
1774
|
+
const files = {};
|
|
1775
|
+
for (const p of paths) {
|
|
1776
|
+
try {
|
|
1777
|
+
const buf = this.readonlyRootFs ? await this.readFileViaExec(container, p) : await this.getFileFromContainer(container, p);
|
|
1778
|
+
files[p] = buf.toString("base64");
|
|
1779
|
+
} catch {}
|
|
1780
|
+
}
|
|
1781
|
+
return files;
|
|
1782
|
+
}
|
|
1783
|
+
async putFile(container, path, content) {
|
|
1784
|
+
if (this.readonlyRootFs) {
|
|
1785
|
+
await this.writeFileViaExec(container, path, content);
|
|
1786
|
+
} else {
|
|
1787
|
+
const tar = createTarBuffer(path, content);
|
|
1788
|
+
await container.putArchive(tar, { path: "/" });
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
async getFile(container, path) {
|
|
1792
|
+
if (this.readonlyRootFs) {
|
|
1793
|
+
return this.readFileViaExec(container, path);
|
|
1794
|
+
}
|
|
1795
|
+
return this.getFileFromContainer(container, path);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
// src/engine/pool.ts
|
|
1799
|
+
init_logger();
|
|
1800
|
+
|
|
1801
|
+
class ContainerPool {
|
|
1802
|
+
docker;
|
|
1803
|
+
poolStrategy;
|
|
1804
|
+
cleanPoolSize;
|
|
1805
|
+
dirtyPoolSize;
|
|
1806
|
+
createOptions;
|
|
1807
|
+
networkMode;
|
|
1808
|
+
securityMode;
|
|
1809
|
+
pools = new Map;
|
|
1810
|
+
replenishing = new Set;
|
|
1811
|
+
pendingReplenishments = new Set;
|
|
1812
|
+
cleaningInterval = null;
|
|
1813
|
+
constructor(options) {
|
|
1814
|
+
this.docker = options.docker;
|
|
1815
|
+
this.poolStrategy = options.poolStrategy ?? "fast";
|
|
1816
|
+
this.createOptions = options.createOptions;
|
|
1817
|
+
this.networkMode = options.networkMode;
|
|
1818
|
+
this.securityMode = options.securityMode;
|
|
1819
|
+
if (typeof options.poolSize === "number") {
|
|
1820
|
+
this.cleanPoolSize = options.poolSize;
|
|
1821
|
+
this.dirtyPoolSize = options.poolSize;
|
|
1822
|
+
} else if (options.poolSize) {
|
|
1823
|
+
this.cleanPoolSize = options.poolSize.clean ?? 1;
|
|
1824
|
+
this.dirtyPoolSize = options.poolSize.dirty ?? 1;
|
|
1825
|
+
} else {
|
|
1826
|
+
this.cleanPoolSize = 1;
|
|
1827
|
+
this.dirtyPoolSize = 1;
|
|
1828
|
+
}
|
|
1829
|
+
if (this.poolStrategy === "fast") {
|
|
1830
|
+
this.startBackgroundCleaning();
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
async acquire(image) {
|
|
1834
|
+
const pool = this.pools.get(image) ?? { clean: [], dirty: [] };
|
|
1835
|
+
if (this.poolStrategy === "fast") {
|
|
1836
|
+
if (pool.clean.length > 0) {
|
|
1837
|
+
const entry = pool.clean.shift();
|
|
1838
|
+
this.pools.set(image, pool);
|
|
1839
|
+
this.replenish(image);
|
|
1840
|
+
return entry.container;
|
|
1841
|
+
}
|
|
1842
|
+
if (pool.dirty.length > 0 && pool.clean.length < this.cleanPoolSize) {
|
|
1843
|
+
await this.cleanDirtyImmediate(image);
|
|
1844
|
+
const updatedPool = this.pools.get(image);
|
|
1845
|
+
if (updatedPool && updatedPool.clean.length > 0) {
|
|
1846
|
+
const entry = updatedPool.clean.shift();
|
|
1847
|
+
this.pools.set(image, updatedPool);
|
|
1848
|
+
this.replenish(image);
|
|
1849
|
+
return entry.container;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return this.createContainer(image);
|
|
1853
|
+
}
|
|
1854
|
+
if (pool.clean && pool.clean.length > 0) {
|
|
1855
|
+
const entry = pool.clean.shift();
|
|
1856
|
+
this.pools.set(image, { clean: pool.clean, dirty: [] });
|
|
1857
|
+
await this.cleanupContainer(entry.container);
|
|
1858
|
+
this.replenish(image);
|
|
1859
|
+
return entry.container;
|
|
1860
|
+
}
|
|
1861
|
+
return this.createContainer(image);
|
|
1862
|
+
}
|
|
1863
|
+
async release(container, image) {
|
|
1864
|
+
let pool = this.pools.get(image);
|
|
1865
|
+
if (!pool) {
|
|
1866
|
+
pool = { clean: [], dirty: [] };
|
|
1867
|
+
this.pools.set(image, pool);
|
|
1868
|
+
}
|
|
1869
|
+
if (this.poolStrategy === "fast") {
|
|
1870
|
+
if (pool.dirty.length >= this.dirtyPoolSize) {
|
|
1871
|
+
await container.remove({ force: true }).catch(() => {});
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
pool.dirty.push({ container, createdAt: Date.now() });
|
|
1875
|
+
} else {
|
|
1876
|
+
if (pool.clean.length >= this.cleanPoolSize) {
|
|
1877
|
+
await container.remove({ force: true }).catch(() => {});
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
if (!pool.clean) {
|
|
1881
|
+
pool.clean = [];
|
|
1882
|
+
}
|
|
1883
|
+
pool.clean.push({ container, createdAt: Date.now() });
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
startBackgroundCleaning() {
|
|
1887
|
+
this.cleaningInterval = setInterval(async () => {
|
|
1888
|
+
for (const [_image, pool] of this.pools) {
|
|
1889
|
+
for (let i = 0;i < this.dirtyPoolSize; i++) {
|
|
1890
|
+
if (pool.dirty.length > 0 && pool.clean.length < this.cleanPoolSize) {
|
|
1891
|
+
const entry = pool.dirty.shift();
|
|
1892
|
+
try {
|
|
1893
|
+
await this.cleanupContainer(entry.container);
|
|
1894
|
+
pool.clean.push(entry);
|
|
1895
|
+
} catch {
|
|
1896
|
+
entry.container.remove({ force: true }).catch(() => {});
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}, 5000);
|
|
1902
|
+
}
|
|
1903
|
+
async cleanDirtyImmediate(image) {
|
|
1904
|
+
const pool = this.pools.get(image);
|
|
1905
|
+
if (!pool || pool.dirty.length === 0 || pool.clean.length >= this.cleanPoolSize) {
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
const entry = pool.dirty.shift();
|
|
1909
|
+
try {
|
|
1910
|
+
await this.cleanupContainer(entry.container);
|
|
1911
|
+
pool.clean.push(entry);
|
|
1912
|
+
} catch {
|
|
1913
|
+
entry.container.remove({ force: true }).catch(() => {});
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
async cleanupContainer(container) {
|
|
1917
|
+
const needsCleanup = this.securityMode === "strict";
|
|
1918
|
+
const needsIptables = this.networkMode === "filtered" && needsCleanup;
|
|
1919
|
+
if (!needsCleanup) {
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
try {
|
|
1923
|
+
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";
|
|
1924
|
+
const cleanExec = await container.exec({
|
|
1925
|
+
Cmd: ["sh", "-c", cleanupCmd]
|
|
1926
|
+
});
|
|
1927
|
+
await cleanExec.start({ Detach: true });
|
|
1928
|
+
let info = await cleanExec.inspect();
|
|
1929
|
+
while (info.Running) {
|
|
1930
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
1931
|
+
info = await cleanExec.inspect();
|
|
1365
1932
|
}
|
|
1366
1933
|
} catch {}
|
|
1367
1934
|
}
|
|
@@ -1472,264 +2039,44 @@ function calculateCPUPercent(stats) {
|
|
|
1472
2039
|
return cpuDelta / systemDelta * numCores * 100;
|
|
1473
2040
|
}
|
|
1474
2041
|
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}`);
|
|
2042
|
+
if (!stats.networks) {
|
|
2043
|
+
return { in: 0, out: 0 };
|
|
2044
|
+
}
|
|
2045
|
+
let rxBytes = 0;
|
|
2046
|
+
let txBytes = 0;
|
|
2047
|
+
for (const iface of Object.values(stats.networks)) {
|
|
2048
|
+
rxBytes += iface.rx_bytes;
|
|
2049
|
+
txBytes += iface.tx_bytes;
|
|
1667
2050
|
}
|
|
2051
|
+
return { in: rxBytes, out: txBytes };
|
|
1668
2052
|
}
|
|
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);
|
|
2053
|
+
async function getContainerStats(container) {
|
|
2054
|
+
const stats = await container.stats({
|
|
2055
|
+
stream: false
|
|
1730
2056
|
});
|
|
2057
|
+
const cpuPercent = calculateCPUPercent(stats);
|
|
2058
|
+
const memoryBytes = stats.memory_stats.usage;
|
|
2059
|
+
const network = calculateNetworkStats(stats);
|
|
2060
|
+
return {
|
|
2061
|
+
cpuPercent: Math.round(cpuPercent * 100) / 100,
|
|
2062
|
+
memoryMB: Math.round(memoryBytes / (1024 * 1024)),
|
|
2063
|
+
networkBytesIn: network.in,
|
|
2064
|
+
networkBytesOut: network.out
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
function calculateResourceDelta(before, after) {
|
|
2068
|
+
return {
|
|
2069
|
+
cpuPercent: after.cpuPercent,
|
|
2070
|
+
memoryMB: after.memoryMB,
|
|
2071
|
+
networkBytesIn: after.networkBytesIn - before.networkBytesIn,
|
|
2072
|
+
networkBytesOut: after.networkBytesOut - before.networkBytesOut
|
|
2073
|
+
};
|
|
1731
2074
|
}
|
|
1732
2075
|
|
|
2076
|
+
// src/engine/docker.ts
|
|
2077
|
+
var SANDBOX_WORKDIR = "/sandbox";
|
|
2078
|
+
var MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
2079
|
+
|
|
1733
2080
|
class DockerIsol8 {
|
|
1734
2081
|
docker;
|
|
1735
2082
|
mode;
|
|
@@ -1754,6 +2101,9 @@ class DockerIsol8 {
|
|
|
1754
2101
|
dependencies;
|
|
1755
2102
|
auditLogger;
|
|
1756
2103
|
remoteCodePolicy;
|
|
2104
|
+
networkManager;
|
|
2105
|
+
executionManager;
|
|
2106
|
+
volumeManager;
|
|
1757
2107
|
container = null;
|
|
1758
2108
|
persistentRuntime = null;
|
|
1759
2109
|
pool = null;
|
|
@@ -1813,6 +2163,18 @@ class DockerIsol8 {
|
|
|
1813
2163
|
if (options.audit) {
|
|
1814
2164
|
this.auditLogger = new AuditLogger(options.audit);
|
|
1815
2165
|
}
|
|
2166
|
+
this.networkManager = new NetworkManager({
|
|
2167
|
+
network: this.network,
|
|
2168
|
+
networkFilter: this.networkFilter
|
|
2169
|
+
});
|
|
2170
|
+
this.executionManager = new ExecutionManager({
|
|
2171
|
+
secrets: this.secrets,
|
|
2172
|
+
maxOutputSize: this.maxOutputSize
|
|
2173
|
+
});
|
|
2174
|
+
this.volumeManager = new VolumeManager({
|
|
2175
|
+
readonlyRootFs: this.readonlyRootFs,
|
|
2176
|
+
sandboxWorkdir: SANDBOX_WORKDIR
|
|
2177
|
+
});
|
|
1816
2178
|
if (options.debug) {
|
|
1817
2179
|
logger.setDebug(true);
|
|
1818
2180
|
}
|
|
@@ -1991,27 +2353,13 @@ class DockerIsol8 {
|
|
|
1991
2353
|
if (!this.container) {
|
|
1992
2354
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
1993
2355
|
}
|
|
1994
|
-
|
|
1995
|
-
await writeFileViaExec(this.container, path, content);
|
|
1996
|
-
} else {
|
|
1997
|
-
const tar = createTarBuffer(path, content);
|
|
1998
|
-
await this.container.putArchive(tar, { path: "/" });
|
|
1999
|
-
}
|
|
2356
|
+
await this.volumeManager.putFile(this.container, path, content);
|
|
2000
2357
|
}
|
|
2001
2358
|
async getFile(path) {
|
|
2002
2359
|
if (!this.container) {
|
|
2003
2360
|
throw new Error("No active container. Call execute() first in persistent mode.");
|
|
2004
2361
|
}
|
|
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);
|
|
2362
|
+
return this.volumeManager.getFile(this.container, path);
|
|
2015
2363
|
}
|
|
2016
2364
|
get containerId() {
|
|
2017
2365
|
return this.container?.id ?? null;
|
|
@@ -2027,26 +2375,24 @@ class DockerIsol8 {
|
|
|
2027
2375
|
Image: image,
|
|
2028
2376
|
Cmd: ["sleep", "infinity"],
|
|
2029
2377
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2030
|
-
Env: this.buildEnv(),
|
|
2378
|
+
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2031
2379
|
NetworkDisabled: this.network === "none",
|
|
2032
2380
|
HostConfig: this.buildHostConfig(),
|
|
2033
2381
|
StopTimeout: 2
|
|
2034
2382
|
});
|
|
2035
2383
|
try {
|
|
2036
2384
|
await container.start();
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
await setupIptables(container);
|
|
2040
|
-
}
|
|
2385
|
+
await this.networkManager.startProxy(container);
|
|
2386
|
+
await this.networkManager.setupIptables(container);
|
|
2041
2387
|
const ext = request.fileExtension ?? adapter.getFileExtension();
|
|
2042
2388
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2043
|
-
await writeFileViaExec(container, filePath, request.code);
|
|
2389
|
+
await this.volumeManager.writeFileViaExec(container, filePath, request.code);
|
|
2044
2390
|
if (request.installPackages?.length) {
|
|
2045
|
-
await installPackages(container, request.runtime, request.installPackages, timeoutMs);
|
|
2391
|
+
await this.executionManager.installPackages(container, request.runtime, request.installPackages, timeoutMs);
|
|
2046
2392
|
}
|
|
2047
2393
|
if (request.files) {
|
|
2048
2394
|
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
2049
|
-
await writeFileViaExec(container, fPath, fContent);
|
|
2395
|
+
await this.volumeManager.writeFileViaExec(container, fPath, fContent);
|
|
2050
2396
|
}
|
|
2051
2397
|
}
|
|
2052
2398
|
const rawCmd = adapter.getCommand(request.code, filePath);
|
|
@@ -2054,22 +2400,22 @@ class DockerIsol8 {
|
|
|
2054
2400
|
let cmd;
|
|
2055
2401
|
if (request.stdin) {
|
|
2056
2402
|
const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
|
|
2057
|
-
await writeFileViaExec(container, stdinPath, request.stdin);
|
|
2403
|
+
await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
|
|
2058
2404
|
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
2059
|
-
cmd = wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2405
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2060
2406
|
} else {
|
|
2061
|
-
cmd = wrapWithTimeout(rawCmd, timeoutSec);
|
|
2407
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2062
2408
|
}
|
|
2063
2409
|
const exec = await container.exec({
|
|
2064
2410
|
Cmd: cmd,
|
|
2065
|
-
Env: this.buildEnv(request.env),
|
|
2411
|
+
Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2066
2412
|
AttachStdout: true,
|
|
2067
2413
|
AttachStderr: true,
|
|
2068
2414
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2069
2415
|
User: "sandbox"
|
|
2070
2416
|
});
|
|
2071
2417
|
const execStream = await exec.start({ Tty: false });
|
|
2072
|
-
yield* this.streamExecOutput(execStream, exec, container, timeoutMs);
|
|
2418
|
+
yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
|
|
2073
2419
|
} finally {
|
|
2074
2420
|
if (this.persist) {
|
|
2075
2421
|
logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
|
|
@@ -2144,7 +2490,7 @@ class DockerIsol8 {
|
|
|
2144
2490
|
createOptions: {
|
|
2145
2491
|
Cmd: ["sleep", "infinity"],
|
|
2146
2492
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2147
|
-
Env: this.buildEnv(),
|
|
2493
|
+
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2148
2494
|
NetworkDisabled: this.network === "none",
|
|
2149
2495
|
HostConfig: this.buildHostConfig(),
|
|
2150
2496
|
StopTimeout: 2
|
|
@@ -2168,10 +2514,8 @@ class DockerIsol8 {
|
|
|
2168
2514
|
}
|
|
2169
2515
|
}
|
|
2170
2516
|
try {
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
await setupIptables(container);
|
|
2174
|
-
}
|
|
2517
|
+
await this.networkManager.startProxy(container);
|
|
2518
|
+
await this.networkManager.setupIptables(container);
|
|
2175
2519
|
const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
|
|
2176
2520
|
let rawCmd;
|
|
2177
2521
|
if (canUseInline) {
|
|
@@ -2180,36 +2524,36 @@ class DockerIsol8 {
|
|
|
2180
2524
|
} catch {
|
|
2181
2525
|
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2182
2526
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2183
|
-
await writeFileViaExec(container, filePath, req.code);
|
|
2527
|
+
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2184
2528
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
2185
2529
|
}
|
|
2186
2530
|
} else {
|
|
2187
2531
|
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2188
2532
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2189
|
-
await writeFileViaExec(container, filePath, req.code);
|
|
2533
|
+
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2190
2534
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
2191
2535
|
}
|
|
2192
2536
|
if (req.installPackages?.length) {
|
|
2193
|
-
await installPackages(container, req.runtime, req.installPackages, timeoutMs);
|
|
2537
|
+
await this.executionManager.installPackages(container, req.runtime, req.installPackages, timeoutMs);
|
|
2194
2538
|
}
|
|
2195
2539
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2196
2540
|
let cmd;
|
|
2197
2541
|
if (req.stdin) {
|
|
2198
2542
|
const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
|
|
2199
|
-
await writeFileViaExec(container, stdinPath, req.stdin);
|
|
2543
|
+
await this.volumeManager.writeFileViaExec(container, stdinPath, req.stdin);
|
|
2200
2544
|
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}' `).join("");
|
|
2201
|
-
cmd = wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2545
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2202
2546
|
} else {
|
|
2203
|
-
cmd = wrapWithTimeout(rawCmd, timeoutSec);
|
|
2547
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2204
2548
|
}
|
|
2205
2549
|
if (req.files) {
|
|
2206
2550
|
for (const [fPath, fContent] of Object.entries(req.files)) {
|
|
2207
|
-
await writeFileViaExec(container, fPath, fContent);
|
|
2551
|
+
await this.volumeManager.writeFileViaExec(container, fPath, fContent);
|
|
2208
2552
|
}
|
|
2209
2553
|
}
|
|
2210
2554
|
const exec = await container.exec({
|
|
2211
2555
|
Cmd: cmd,
|
|
2212
|
-
Env: this.buildEnv(req.env),
|
|
2556
|
+
Env: this.executionManager.buildEnv(req.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2213
2557
|
AttachStdout: true,
|
|
2214
2558
|
AttachStderr: true,
|
|
2215
2559
|
WorkingDir: SANDBOX_WORKDIR,
|
|
@@ -2217,7 +2561,7 @@ class DockerIsol8 {
|
|
|
2217
2561
|
});
|
|
2218
2562
|
const start = performance.now();
|
|
2219
2563
|
const execStream = await exec.start({ Tty: false });
|
|
2220
|
-
const { stdout, stderr, truncated } = await this.collectExecOutput(execStream, container, timeoutMs);
|
|
2564
|
+
const { stdout, stderr, truncated } = await this.executionManager.collectExecOutput(execStream, container, timeoutMs);
|
|
2221
2565
|
const durationMs = Math.round(performance.now() - start);
|
|
2222
2566
|
const inspectResult = await exec.inspect();
|
|
2223
2567
|
let resourceUsage;
|
|
@@ -2241,8 +2585,8 @@ class DockerIsol8 {
|
|
|
2241
2585
|
}
|
|
2242
2586
|
}
|
|
2243
2587
|
const result = {
|
|
2244
|
-
stdout: this.postProcessOutput(stdout, truncated),
|
|
2245
|
-
stderr: this.postProcessOutput(stderr, false),
|
|
2588
|
+
stdout: this.executionManager.postProcessOutput(stdout, truncated),
|
|
2589
|
+
stderr: this.executionManager.postProcessOutput(stderr, false),
|
|
2246
2590
|
exitCode: inspectResult.ExitCode ?? 1,
|
|
2247
2591
|
durationMs,
|
|
2248
2592
|
truncated,
|
|
@@ -2252,7 +2596,7 @@ class DockerIsol8 {
|
|
|
2252
2596
|
containerId: container.id,
|
|
2253
2597
|
...resourceUsage ? { resourceUsage } : {},
|
|
2254
2598
|
...networkLogs ? { networkLogs } : {},
|
|
2255
|
-
...req.outputPaths ? { files: await this.retrieveFiles(container, req.outputPaths) } : {}
|
|
2599
|
+
...req.outputPaths ? { files: await this.volumeManager.retrieveFiles(container, req.outputPaths) } : {}
|
|
2256
2600
|
};
|
|
2257
2601
|
if (this.auditLogger) {
|
|
2258
2602
|
await this.recordAudit(req, result, startTime, container);
|
|
@@ -2279,37 +2623,27 @@ class DockerIsol8 {
|
|
|
2279
2623
|
}
|
|
2280
2624
|
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2281
2625
|
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
|
-
}
|
|
2626
|
+
await this.volumeManager.putFile(this.container, filePath, req.code);
|
|
2288
2627
|
if (req.files) {
|
|
2289
2628
|
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
|
-
}
|
|
2629
|
+
await this.volumeManager.putFile(this.container, fPath, fContent);
|
|
2296
2630
|
}
|
|
2297
2631
|
}
|
|
2298
2632
|
const rawCmd = adapter.getCommand(req.code, filePath);
|
|
2299
2633
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2300
2634
|
if (req.installPackages?.length) {
|
|
2301
|
-
await installPackages(this.container, req.runtime, req.installPackages, timeoutMs);
|
|
2635
|
+
await this.executionManager.installPackages(this.container, req.runtime, req.installPackages, timeoutMs);
|
|
2302
2636
|
}
|
|
2303
2637
|
let cmd;
|
|
2304
2638
|
if (req.stdin) {
|
|
2305
2639
|
const stdinPath = `${SANDBOX_WORKDIR}/_stdin_${Date.now()}`;
|
|
2306
|
-
await writeFileViaExec(this.container, stdinPath, req.stdin);
|
|
2640
|
+
await this.volumeManager.writeFileViaExec(this.container, stdinPath, req.stdin);
|
|
2307
2641
|
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}' `).join("");
|
|
2308
|
-
cmd = wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2642
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2309
2643
|
} else {
|
|
2310
|
-
cmd = wrapWithTimeout(rawCmd, timeoutSec);
|
|
2644
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2311
2645
|
}
|
|
2312
|
-
const execEnv = this.buildEnv(req.env);
|
|
2646
|
+
const execEnv = this.executionManager.buildEnv(req.env, this.networkManager.proxyPort, this.network, this.networkFilter);
|
|
2313
2647
|
const exec = await this.container.exec({
|
|
2314
2648
|
Cmd: cmd,
|
|
2315
2649
|
Env: execEnv,
|
|
@@ -2320,7 +2654,7 @@ class DockerIsol8 {
|
|
|
2320
2654
|
});
|
|
2321
2655
|
const start = performance.now();
|
|
2322
2656
|
const execStream = await exec.start({ Tty: false });
|
|
2323
|
-
const { stdout, stderr, truncated } = await this.collectExecOutput(execStream, this.container, timeoutMs);
|
|
2657
|
+
const { stdout, stderr, truncated } = await this.executionManager.collectExecOutput(execStream, this.container, timeoutMs);
|
|
2324
2658
|
const durationMs = Math.round(performance.now() - start);
|
|
2325
2659
|
const inspectResult = await exec.inspect();
|
|
2326
2660
|
let resourceUsage;
|
|
@@ -2349,8 +2683,8 @@ class DockerIsol8 {
|
|
|
2349
2683
|
}
|
|
2350
2684
|
}
|
|
2351
2685
|
const result = {
|
|
2352
|
-
stdout: this.postProcessOutput(stdout, truncated),
|
|
2353
|
-
stderr: this.postProcessOutput(stderr, false),
|
|
2686
|
+
stdout: this.executionManager.postProcessOutput(stdout, truncated),
|
|
2687
|
+
stderr: this.executionManager.postProcessOutput(stderr, false),
|
|
2354
2688
|
exitCode: inspectResult.ExitCode ?? 1,
|
|
2355
2689
|
durationMs,
|
|
2356
2690
|
truncated,
|
|
@@ -2368,22 +2702,7 @@ class DockerIsol8 {
|
|
|
2368
2702
|
return result;
|
|
2369
2703
|
}
|
|
2370
2704
|
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);
|
|
2705
|
+
return this.volumeManager.retrieveFiles(container, paths);
|
|
2387
2706
|
}
|
|
2388
2707
|
async startPersistentContainer(adapter) {
|
|
2389
2708
|
const image = await this.resolveImage(adapter);
|
|
@@ -2391,7 +2710,7 @@ class DockerIsol8 {
|
|
|
2391
2710
|
Image: image,
|
|
2392
2711
|
Cmd: ["sleep", "infinity"],
|
|
2393
2712
|
WorkingDir: SANDBOX_WORKDIR,
|
|
2394
|
-
Env: this.buildEnv(),
|
|
2713
|
+
Env: this.executionManager.buildEnv(undefined, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2395
2714
|
NetworkDisabled: this.network === "none",
|
|
2396
2715
|
HostConfig: this.buildHostConfig(),
|
|
2397
2716
|
StopTimeout: 2,
|
|
@@ -2401,10 +2720,8 @@ class DockerIsol8 {
|
|
|
2401
2720
|
}
|
|
2402
2721
|
});
|
|
2403
2722
|
await this.container.start();
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
await setupIptables(this.container);
|
|
2407
|
-
}
|
|
2723
|
+
await this.networkManager.startProxy(this.container);
|
|
2724
|
+
await this.networkManager.setupIptables(this.container);
|
|
2408
2725
|
this.persistentRuntime = adapter;
|
|
2409
2726
|
}
|
|
2410
2727
|
getAdapter(runtime) {
|
|
@@ -2468,181 +2785,6 @@ class DockerIsol8 {
|
|
|
2468
2785
|
}
|
|
2469
2786
|
throw new Error("Embedded default seccomp profile is unavailable");
|
|
2470
2787
|
}
|
|
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
2788
|
static async cleanup(docker) {
|
|
2647
2789
|
const dockerInstance = docker ?? new Docker;
|
|
2648
2790
|
const containers = await dockerInstance.listContainers({ all: true });
|
|
@@ -2693,8 +2835,8 @@ init_logger();
|
|
|
2693
2835
|
// package.json
|
|
2694
2836
|
var package_default = {
|
|
2695
2837
|
name: "@isol8/core",
|
|
2696
|
-
version: "0.
|
|
2697
|
-
description: "
|
|
2838
|
+
version: "0.15.0",
|
|
2839
|
+
description: "Sandboxed code execution engine for AI agents and apps (Docker, runtime and network controls)",
|
|
2698
2840
|
author: "Illusion47586",
|
|
2699
2841
|
license: "MIT",
|
|
2700
2842
|
repository: {
|
|
@@ -2740,6 +2882,14 @@ var package_default = {
|
|
|
2740
2882
|
"schema",
|
|
2741
2883
|
"docker"
|
|
2742
2884
|
],
|
|
2885
|
+
keywords: [
|
|
2886
|
+
"isol8",
|
|
2887
|
+
"ai-agent",
|
|
2888
|
+
"sandbox",
|
|
2889
|
+
"secure-execution",
|
|
2890
|
+
"docker",
|
|
2891
|
+
"typescript"
|
|
2892
|
+
],
|
|
2743
2893
|
jsonValidation: [
|
|
2744
2894
|
{
|
|
2745
2895
|
fileMatch: "isol8.config.json",
|
|
@@ -2769,4 +2919,4 @@ export {
|
|
|
2769
2919
|
BunAdapter
|
|
2770
2920
|
};
|
|
2771
2921
|
|
|
2772
|
-
//# debugId=
|
|
2922
|
+
//# debugId=F3E07734F4ED9C3E64756E2164756E21
|