@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/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: (newValue) => all[name] = () => newValue
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/pool.ts
1326
+ // src/engine/managers/execution-manager.ts
1232
1327
  init_logger();
1328
+ import { PassThrough } from "node:stream";
1233
1329
 
1234
- class ContainerPool {
1235
- docker;
1236
- poolStrategy;
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.docker = options.docker;
1248
- this.poolStrategy = options.poolStrategy ?? "fast";
1249
- this.createOptions = options.createOptions;
1250
- this.networkMode = options.networkMode;
1251
- this.securityMode = options.securityMode;
1252
- if (typeof options.poolSize === "number") {
1253
- this.cleanPoolSize = options.poolSize;
1254
- this.dirtyPoolSize = options.poolSize;
1255
- } else if (options.poolSize) {
1256
- this.cleanPoolSize = options.poolSize.clean ?? 1;
1257
- this.dirtyPoolSize = options.poolSize.dirty ?? 1;
1258
- } else {
1259
- this.cleanPoolSize = 1;
1260
- this.dirtyPoolSize = 1;
1261
- }
1262
- if (this.poolStrategy === "fast") {
1263
- this.startBackgroundCleaning();
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 acquire(image) {
1267
- const pool = this.pools.get(image) ?? { clean: [], dirty: [] };
1268
- if (this.poolStrategy === "fast") {
1269
- if (pool.clean.length > 0) {
1270
- const entry = pool.clean.shift();
1271
- this.pools.set(image, pool);
1272
- this.replenish(image);
1273
- return entry.container;
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
- if (pool.dirty.length > 0 && pool.clean.length < this.cleanPoolSize) {
1276
- await this.cleanDirtyImmediate(image);
1277
- const updatedPool = this.pools.get(image);
1278
- if (updatedPool && updatedPool.clean.length > 0) {
1279
- const entry = updatedPool.clean.shift();
1280
- this.pools.set(image, updatedPool);
1281
- this.replenish(image);
1282
- return entry.container;
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
- return this.createContainer(image);
1286
- }
1287
- if (pool.clean && pool.clean.length > 0) {
1288
- const entry = pool.clean.shift();
1289
- this.pools.set(image, { clean: pool.clean, dirty: [] });
1290
- await this.cleanupContainer(entry.container);
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
- pool.dirty.push({ container, createdAt: Date.now() });
1308
- } else {
1309
- if (pool.clean.length >= this.cleanPoolSize) {
1310
- await container.remove({ force: true }).catch(() => {});
1311
- return;
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
- if (!pool.clean) {
1314
- pool.clean = [];
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
- startBackgroundCleaning() {
1320
- this.cleaningInterval = setInterval(async () => {
1321
- for (const [_image, pool] of this.pools) {
1322
- for (let i = 0;i < this.dirtyPoolSize; i++) {
1323
- if (pool.dirty.length > 0 && pool.clean.length < this.cleanPoolSize) {
1324
- const entry = pool.dirty.shift();
1325
- try {
1326
- await this.cleanupContainer(entry.container);
1327
- pool.clean.push(entry);
1328
- } catch {
1329
- entry.container.remove({ force: true }).catch(() => {});
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
- }, 5000);
1335
- }
1336
- async cleanDirtyImmediate(image) {
1337
- const pool = this.pools.get(image);
1338
- if (!pool || pool.dirty.length === 0 || pool.clean.length >= this.cleanPoolSize) {
1339
- return;
1340
- }
1341
- const entry = pool.dirty.shift();
1342
- try {
1343
- await this.cleanupContainer(entry.container);
1344
- pool.clean.push(entry);
1345
- } catch {
1346
- entry.container.remove({ force: true }).catch(() => {});
1347
- }
1348
- }
1349
- async cleanupContainer(container) {
1350
- const needsCleanup = this.securityMode === "strict";
1351
- const needsIptables = this.networkMode === "filtered" && needsCleanup;
1352
- if (!needsCleanup) {
1353
- return;
1354
- }
1355
- try {
1356
- 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";
1357
- const cleanExec = await container.exec({
1358
- Cmd: ["sh", "-c", cleanupCmd]
1359
- });
1360
- await cleanExec.start({ Detach: true });
1361
- let info = await cleanExec.inspect();
1362
- while (info.Running) {
1363
- await new Promise((r) => setTimeout(r, 5));
1364
- info = await cleanExec.inspect();
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 installPackages(container, runtime, packages, timeoutMs) {
1670
- const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));
1671
- const cmd = wrapWithTimeout(getInstallCommand(runtime, packages), timeoutSec);
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
- if (this.readonlyRootFs) {
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
- if (this.readonlyRootFs) {
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
- if (this.network === "filtered") {
2038
- await startProxy(container, this.networkFilter);
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
- if (this.network === "filtered") {
2172
- await startProxy(container, this.networkFilter);
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
- if (this.readonlyRootFs) {
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
- if (this.readonlyRootFs) {
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
- const files = {};
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
- if (this.network === "filtered") {
2405
- await startProxy(this.container, this.networkFilter);
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.14.6",
2697
- description: "Core engine for isol8 secure code execution",
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=797D9B1ED830A1AA64756E2164756E21
2922
+ //# debugId=F3E07734F4ED9C3E64756E2164756E21