@isol8/core 0.18.0 → 0.20.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/docker/Dockerfile +13 -1
- package/dist/engine/docker.d.ts +26 -0
- package/dist/engine/docker.d.ts.map +1 -1
- package/dist/engine/managers/execution-manager.d.ts +1 -1
- package/dist/engine/managers/execution-manager.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +379 -123
- package/dist/index.js.map +10 -9
- package/dist/runtime/adapter.d.ts +20 -0
- package/dist/runtime/adapter.d.ts.map +1 -1
- package/dist/runtime/adapters/agent.d.ts +19 -0
- package/dist/runtime/adapters/agent.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +3 -2
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/types.d.ts +39 -8
- package/dist/types.d.ts.map +1 -1
- package/docker/Dockerfile +13 -1
- package/package.json +1 -1
- package/schema/isol8.config.schema.json +39 -0
package/dist/index.js
CHANGED
|
@@ -48,6 +48,37 @@ var init_adapter = __esm(() => {
|
|
|
48
48
|
};
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
// src/runtime/adapters/agent.ts
|
|
52
|
+
function shellQuote(s) {
|
|
53
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
54
|
+
}
|
|
55
|
+
var SANDBOX_SYSTEM_PROMPT, AgentAdapter;
|
|
56
|
+
var init_agent = __esm(() => {
|
|
57
|
+
SANDBOX_SYSTEM_PROMPT = "You are running inside an isol8 sandbox — a Docker container with strict " + "resource limits and controlled network access. isol8 exists to execute " + "untrusted code safely: outbound network is filtered to a whitelist, the " + "filesystem is ephemeral, and some system calls are restricted. Work within " + "these constraints: do not assume open internet access, do not rely on " + "persistent state across runs, and do not attempt to escape the sandbox.";
|
|
58
|
+
AgentAdapter = {
|
|
59
|
+
name: "agent",
|
|
60
|
+
image: "isol8:agent",
|
|
61
|
+
getCommand(code) {
|
|
62
|
+
return [
|
|
63
|
+
"bash",
|
|
64
|
+
"-c",
|
|
65
|
+
`pi --no-session --append-system-prompt ${shellQuote(SANDBOX_SYSTEM_PROMPT)} -p ${shellQuote(code)}`
|
|
66
|
+
];
|
|
67
|
+
},
|
|
68
|
+
getCommandWithOptions(code, options) {
|
|
69
|
+
const flags = options.agentFlags ? `${options.agentFlags} ` : "";
|
|
70
|
+
return [
|
|
71
|
+
"bash",
|
|
72
|
+
"-c",
|
|
73
|
+
`pi --no-session --append-system-prompt ${shellQuote(SANDBOX_SYSTEM_PROMPT)} ${flags}-p ${shellQuote(code)}`
|
|
74
|
+
];
|
|
75
|
+
},
|
|
76
|
+
getFileExtension() {
|
|
77
|
+
return ".txt";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
51
82
|
// src/runtime/adapters/bash.ts
|
|
52
83
|
var bashAdapter;
|
|
53
84
|
var init_bash = __esm(() => {
|
|
@@ -149,12 +180,14 @@ var init_python = __esm(() => {
|
|
|
149
180
|
// src/runtime/index.ts
|
|
150
181
|
var init_runtime = __esm(() => {
|
|
151
182
|
init_adapter();
|
|
183
|
+
init_agent();
|
|
152
184
|
init_bash();
|
|
153
185
|
init_bun();
|
|
154
186
|
init_deno();
|
|
155
187
|
init_node();
|
|
156
188
|
init_python();
|
|
157
189
|
init_adapter();
|
|
190
|
+
init_agent();
|
|
158
191
|
init_bash();
|
|
159
192
|
init_bun();
|
|
160
193
|
init_deno();
|
|
@@ -165,6 +198,7 @@ var init_runtime = __esm(() => {
|
|
|
165
198
|
RuntimeRegistry.register(BunAdapter);
|
|
166
199
|
RuntimeRegistry.register(bashAdapter);
|
|
167
200
|
RuntimeRegistry.register(DenoAdapter);
|
|
201
|
+
RuntimeRegistry.register(AgentAdapter);
|
|
168
202
|
});
|
|
169
203
|
|
|
170
204
|
// src/utils/logger.ts
|
|
@@ -530,9 +564,9 @@ ${setupLines}
|
|
|
530
564
|
}
|
|
531
565
|
}, (event) => {
|
|
532
566
|
if (event.stream) {
|
|
533
|
-
|
|
567
|
+
onProgress?.({ runtime: String(runtime), status: "building", message: event.stream });
|
|
534
568
|
} else if (event.error) {
|
|
535
|
-
|
|
569
|
+
onProgress?.({ runtime: String(runtime), status: "error", message: event.error });
|
|
536
570
|
}
|
|
537
571
|
});
|
|
538
572
|
});
|
|
@@ -1384,6 +1418,8 @@ class ExecutionManager {
|
|
|
1384
1418
|
return ["npm", "install", "--prefix", "/sandbox", ...packages];
|
|
1385
1419
|
case "bun":
|
|
1386
1420
|
return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
|
|
1421
|
+
case "agent":
|
|
1422
|
+
return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
|
|
1387
1423
|
case "deno":
|
|
1388
1424
|
return ["sh", "-c", packages.map((p) => `deno cache ${p}`).join(" && ")];
|
|
1389
1425
|
case "bash":
|
|
@@ -1411,7 +1447,7 @@ class ExecutionManager {
|
|
|
1411
1447
|
env.push("npm_config_fetch_retry_mintimeout=1000");
|
|
1412
1448
|
env.push("NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=2000");
|
|
1413
1449
|
env.push("npm_config_fetch_retry_maxtimeout=2000");
|
|
1414
|
-
} else if (runtime === "bun") {
|
|
1450
|
+
} else if (runtime === "bun" || runtime === "agent") {
|
|
1415
1451
|
env.push("BUN_INSTALL_GLOBAL_DIR=/sandbox/.bun-global");
|
|
1416
1452
|
env.push("BUN_INSTALL_CACHE_DIR=/sandbox/.bun-cache");
|
|
1417
1453
|
env.push("BUN_INSTALL_BIN=/sandbox/.bun-global/bin");
|
|
@@ -1455,7 +1491,7 @@ class ExecutionManager {
|
|
|
1455
1491
|
stream.on("error", reject);
|
|
1456
1492
|
});
|
|
1457
1493
|
}
|
|
1458
|
-
async runSetupScript(container, script, timeoutMs, volumeManager) {
|
|
1494
|
+
async* runSetupScript(container, script, timeoutMs, volumeManager) {
|
|
1459
1495
|
const scriptPath = "/sandbox/.isol8-setup.sh";
|
|
1460
1496
|
await volumeManager.writeFileViaExec(container, scriptPath, script);
|
|
1461
1497
|
const chmodExec = await container.exec({
|
|
@@ -1483,34 +1519,67 @@ class ExecutionManager {
|
|
|
1483
1519
|
User: "sandbox"
|
|
1484
1520
|
});
|
|
1485
1521
|
const stream = await exec.start({ Detach: false, Tty: false });
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1522
|
+
const queue = [];
|
|
1523
|
+
let notify = null;
|
|
1524
|
+
let done = false;
|
|
1525
|
+
const push = (event) => {
|
|
1526
|
+
queue.push(event);
|
|
1527
|
+
if (notify) {
|
|
1528
|
+
notify();
|
|
1529
|
+
notify = null;
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
const timer = setTimeout(() => {
|
|
1533
|
+
push({ type: "error", data: "SETUP SCRIPT TIMED OUT", phase: "setup" });
|
|
1534
|
+
push({ type: "exit", data: "137", phase: "setup" });
|
|
1535
|
+
done = true;
|
|
1536
|
+
}, timeoutMs);
|
|
1537
|
+
const stdoutStream = new PassThrough;
|
|
1538
|
+
const stderrStream = new PassThrough;
|
|
1539
|
+
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1540
|
+
stdoutStream.on("data", (chunk) => {
|
|
1541
|
+
const text = chunk.toString("utf-8");
|
|
1542
|
+
logger.debug(`[setup:stdout] ${text.trimEnd()}`);
|
|
1543
|
+
push({ type: "stdout", data: text, phase: "setup" });
|
|
1544
|
+
});
|
|
1545
|
+
stderrStream.on("data", (chunk) => {
|
|
1546
|
+
const text = chunk.toString("utf-8");
|
|
1547
|
+
logger.debug(`[setup:stderr] ${text.trimEnd()}`);
|
|
1548
|
+
push({ type: "stderr", data: text, phase: "setup" });
|
|
1549
|
+
});
|
|
1550
|
+
stream.on("end", async () => {
|
|
1551
|
+
clearTimeout(timer);
|
|
1552
|
+
try {
|
|
1553
|
+
const info = await exec.inspect();
|
|
1554
|
+
const exitCode = info.ExitCode ?? 0;
|
|
1555
|
+
if (exitCode !== 0) {
|
|
1556
|
+
push({
|
|
1557
|
+
type: "error",
|
|
1558
|
+
data: `Setup script failed (exit code ${exitCode})`,
|
|
1559
|
+
phase: "setup"
|
|
1560
|
+
});
|
|
1510
1561
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1562
|
+
push({ type: "exit", data: exitCode.toString(), phase: "setup" });
|
|
1563
|
+
} catch {
|
|
1564
|
+
push({ type: "exit", data: "1", phase: "setup" });
|
|
1565
|
+
}
|
|
1566
|
+
done = true;
|
|
1567
|
+
});
|
|
1568
|
+
stream.on("error", (err) => {
|
|
1569
|
+
clearTimeout(timer);
|
|
1570
|
+
push({ type: "error", data: err.message, phase: "setup" });
|
|
1571
|
+
push({ type: "exit", data: "1", phase: "setup" });
|
|
1572
|
+
done = true;
|
|
1513
1573
|
});
|
|
1574
|
+
while (!done || queue.length > 0) {
|
|
1575
|
+
if (queue.length > 0) {
|
|
1576
|
+
yield queue.shift();
|
|
1577
|
+
} else {
|
|
1578
|
+
await new Promise((r) => {
|
|
1579
|
+
notify = r;
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1514
1583
|
}
|
|
1515
1584
|
async* streamExecOutput(stream, exec, container, timeoutMs) {
|
|
1516
1585
|
const queue = [];
|
|
@@ -1524,8 +1593,8 @@ class ExecutionManager {
|
|
|
1524
1593
|
}
|
|
1525
1594
|
};
|
|
1526
1595
|
const timer = setTimeout(() => {
|
|
1527
|
-
push({ type: "error", data: "EXECUTION TIMED OUT" });
|
|
1528
|
-
push({ type: "exit", data: "137" });
|
|
1596
|
+
push({ type: "error", data: "EXECUTION TIMED OUT", phase: "code" });
|
|
1597
|
+
push({ type: "exit", data: "137", phase: "code" });
|
|
1529
1598
|
done = true;
|
|
1530
1599
|
}, timeoutMs);
|
|
1531
1600
|
const stdoutStream = new PassThrough;
|
|
@@ -1536,29 +1605,29 @@ class ExecutionManager {
|
|
|
1536
1605
|
if (Object.keys(this.secrets).length > 0) {
|
|
1537
1606
|
text = maskSecrets(text, this.secrets);
|
|
1538
1607
|
}
|
|
1539
|
-
push({ type: "stdout", data: text });
|
|
1608
|
+
push({ type: "stdout", data: text, phase: "code" });
|
|
1540
1609
|
});
|
|
1541
1610
|
stderrStream.on("data", (chunk) => {
|
|
1542
1611
|
let text = chunk.toString("utf-8");
|
|
1543
1612
|
if (Object.keys(this.secrets).length > 0) {
|
|
1544
1613
|
text = maskSecrets(text, this.secrets);
|
|
1545
1614
|
}
|
|
1546
|
-
push({ type: "stderr", data: text });
|
|
1615
|
+
push({ type: "stderr", data: text, phase: "code" });
|
|
1547
1616
|
});
|
|
1548
1617
|
stream.on("end", async () => {
|
|
1549
1618
|
clearTimeout(timer);
|
|
1550
1619
|
try {
|
|
1551
1620
|
const info = await exec.inspect();
|
|
1552
|
-
push({ type: "exit", data: (info.ExitCode ?? 0).toString() });
|
|
1621
|
+
push({ type: "exit", data: (info.ExitCode ?? 0).toString(), phase: "code" });
|
|
1553
1622
|
} catch {
|
|
1554
|
-
push({ type: "exit", data: "1" });
|
|
1623
|
+
push({ type: "exit", data: "1", phase: "code" });
|
|
1555
1624
|
}
|
|
1556
1625
|
done = true;
|
|
1557
1626
|
});
|
|
1558
1627
|
stream.on("error", (err) => {
|
|
1559
1628
|
clearTimeout(timer);
|
|
1560
|
-
push({ type: "error", data: err.message });
|
|
1561
|
-
push({ type: "exit", data: "1" });
|
|
1629
|
+
push({ type: "error", data: err.message, phase: "code" });
|
|
1630
|
+
push({ type: "exit", data: "1", phase: "code" });
|
|
1562
1631
|
done = true;
|
|
1563
1632
|
});
|
|
1564
1633
|
while (!done || queue.length > 0) {
|
|
@@ -2196,11 +2265,16 @@ class DockerIsol8 {
|
|
|
2196
2265
|
async resolveExecutionRequest(req) {
|
|
2197
2266
|
const inlineCode = req.code?.trim();
|
|
2198
2267
|
const codeUrl = req.codeUrl?.trim();
|
|
2199
|
-
|
|
2200
|
-
|
|
2268
|
+
const cmd = req.cmd?.trim();
|
|
2269
|
+
const sourceCount = [inlineCode, codeUrl, cmd].filter(Boolean).length;
|
|
2270
|
+
if (sourceCount > 1) {
|
|
2271
|
+
throw new Error("ExecutionRequest.code, ExecutionRequest.codeUrl, and ExecutionRequest.cmd are mutually exclusive — provide exactly one.");
|
|
2201
2272
|
}
|
|
2202
|
-
if (
|
|
2203
|
-
throw new Error("ExecutionRequest must include
|
|
2273
|
+
if (sourceCount === 0) {
|
|
2274
|
+
throw new Error("ExecutionRequest must include exactly one of: code, codeUrl, or cmd.");
|
|
2275
|
+
}
|
|
2276
|
+
if (cmd) {
|
|
2277
|
+
return { ...req, cmd: req.cmd };
|
|
2204
2278
|
}
|
|
2205
2279
|
if (inlineCode) {
|
|
2206
2280
|
return { ...req, code: req.code };
|
|
@@ -2311,6 +2385,7 @@ class DockerIsol8 {
|
|
|
2311
2385
|
await this.semaphore.acquire();
|
|
2312
2386
|
const startTime = Date.now();
|
|
2313
2387
|
try {
|
|
2388
|
+
this.validateAgentRuntime(req);
|
|
2314
2389
|
const request = await this.resolveExecutionRequest(req);
|
|
2315
2390
|
const result = this.mode === "persistent" ? await this.executePersistent(request, startTime) : await this.executeEphemeral(request, startTime);
|
|
2316
2391
|
return result;
|
|
@@ -2321,7 +2396,7 @@ class DockerIsol8 {
|
|
|
2321
2396
|
async recordAudit(req, result, startTime, container) {
|
|
2322
2397
|
try {
|
|
2323
2398
|
const enc = new TextEncoder;
|
|
2324
|
-
const data = enc.encode(req.code);
|
|
2399
|
+
const data = enc.encode(req.code ?? req.cmd ?? "");
|
|
2325
2400
|
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
2326
2401
|
const codeHash = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2327
2402
|
let securityEvents;
|
|
@@ -2452,74 +2527,173 @@ class DockerIsol8 {
|
|
|
2452
2527
|
async* executeStream(req) {
|
|
2453
2528
|
await this.semaphore.acquire();
|
|
2454
2529
|
try {
|
|
2530
|
+
this.validateAgentRuntime(req);
|
|
2455
2531
|
const request = await this.resolveExecutionRequest(req);
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2532
|
+
if (this.mode === "persistent") {
|
|
2533
|
+
yield* this.executeStreamPersistent(request);
|
|
2534
|
+
} else {
|
|
2535
|
+
yield* this.executeStreamEphemeral(request);
|
|
2536
|
+
}
|
|
2537
|
+
} finally {
|
|
2538
|
+
this.semaphore.release();
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
async* executeStreamPersistent(request) {
|
|
2542
|
+
const adapter = this.getAdapter(request.runtime);
|
|
2543
|
+
const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
|
|
2544
|
+
const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
|
|
2545
|
+
let remainingPackages = request.installPackages ?? [];
|
|
2546
|
+
let imageSetupScript;
|
|
2547
|
+
if (!this.container) {
|
|
2548
|
+
const started = await this.startPersistentContainer(adapter, request.installPackages);
|
|
2549
|
+
remainingPackages = started.remainingPackages;
|
|
2550
|
+
imageSetupScript = started.imageSetupScript;
|
|
2551
|
+
} else if (this.persistentRuntime?.name !== adapter.name) {
|
|
2552
|
+
throw new Error(`Cannot switch runtime from "${this.persistentRuntime?.name}" to "${adapter.name}". Each persistent container supports a single runtime. Create a new Isol8 instance for a different runtime.`);
|
|
2553
|
+
}
|
|
2554
|
+
let rawCmd;
|
|
2555
|
+
if (request.cmd) {
|
|
2556
|
+
rawCmd = ["bash", "-c", request.cmd];
|
|
2557
|
+
} else {
|
|
2558
|
+
const ext = request.fileExtension ?? adapter.getFileExtension();
|
|
2559
|
+
const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
|
|
2560
|
+
await this.volumeManager.putFile(this.container, filePath, request.code);
|
|
2561
|
+
rawCmd = this.buildAdapterCommand(adapter, request, filePath);
|
|
2562
|
+
}
|
|
2563
|
+
if (request.files) {
|
|
2564
|
+
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
2565
|
+
await this.volumeManager.putFile(this.container, fPath, fContent);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
if (remainingPackages.length > 0) {
|
|
2569
|
+
await this.executionManager.installPackages(this.container, request.runtime, remainingPackages, timeoutMs);
|
|
2570
|
+
}
|
|
2571
|
+
if (imageSetupScript) {
|
|
2572
|
+
let setupExitCode = 0;
|
|
2573
|
+
for await (const event of this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager)) {
|
|
2574
|
+
yield event;
|
|
2575
|
+
if (event.type === "exit") {
|
|
2576
|
+
setupExitCode = Number(event.data);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
if (setupExitCode !== 0) {
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
if (request.setupScript) {
|
|
2584
|
+
let setupExitCode = 0;
|
|
2585
|
+
for await (const event of this.executionManager.runSetupScript(this.container, request.setupScript, timeoutMs, this.volumeManager)) {
|
|
2586
|
+
yield event;
|
|
2587
|
+
if (event.type === "exit") {
|
|
2588
|
+
setupExitCode = Number(event.data);
|
|
2479
2589
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2590
|
+
}
|
|
2591
|
+
if (setupExitCode !== 0) {
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2596
|
+
let cmd;
|
|
2597
|
+
if (request.stdin) {
|
|
2598
|
+
const stdinPath = `${SANDBOX_WORKDIR}/_stdin_${Date.now()}`;
|
|
2599
|
+
await this.volumeManager.writeFileViaExec(this.container, stdinPath, request.stdin);
|
|
2600
|
+
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
2601
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2602
|
+
} else {
|
|
2603
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2604
|
+
}
|
|
2605
|
+
const exec = await this.container.exec({
|
|
2606
|
+
Cmd: cmd,
|
|
2607
|
+
Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2608
|
+
AttachStdout: true,
|
|
2609
|
+
AttachStderr: true,
|
|
2610
|
+
WorkingDir: execWorkdir,
|
|
2611
|
+
User: "sandbox"
|
|
2612
|
+
});
|
|
2613
|
+
const execStream = await exec.start({ Tty: false });
|
|
2614
|
+
yield* this.executionManager.streamExecOutput(execStream, exec, this.container, timeoutMs);
|
|
2615
|
+
}
|
|
2616
|
+
async* executeStreamEphemeral(request) {
|
|
2617
|
+
const adapter = this.getAdapter(request.runtime);
|
|
2618
|
+
const timeoutMs = request.timeoutMs ?? this.defaultTimeoutMs;
|
|
2619
|
+
const resolved = await this.resolveImage(adapter, request.installPackages);
|
|
2620
|
+
const image = resolved.image;
|
|
2621
|
+
const execWorkdir = request.workdir ? resolveWorkdir(request.workdir) : SANDBOX_WORKDIR;
|
|
2622
|
+
const pool = this.ensurePool();
|
|
2623
|
+
const container = await pool.acquire(image);
|
|
2624
|
+
try {
|
|
2625
|
+
await this.networkManager.startProxy(container);
|
|
2626
|
+
await this.networkManager.setupIptables(container);
|
|
2627
|
+
if (resolved.remainingPackages.length > 0) {
|
|
2628
|
+
await this.executionManager.installPackages(container, request.runtime, resolved.remainingPackages, timeoutMs);
|
|
2629
|
+
}
|
|
2630
|
+
if (resolved.imageSetupScript) {
|
|
2631
|
+
let setupExitCode = 0;
|
|
2632
|
+
for await (const event of this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager)) {
|
|
2633
|
+
yield event;
|
|
2634
|
+
if (event.type === "exit") {
|
|
2635
|
+
setupExitCode = Number(event.data);
|
|
2636
|
+
}
|
|
2482
2637
|
}
|
|
2483
|
-
if (
|
|
2484
|
-
|
|
2638
|
+
if (setupExitCode !== 0) {
|
|
2639
|
+
return;
|
|
2485
2640
|
}
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2641
|
+
}
|
|
2642
|
+
if (request.setupScript) {
|
|
2643
|
+
let setupExitCode = 0;
|
|
2644
|
+
for await (const event of this.executionManager.runSetupScript(container, request.setupScript, timeoutMs, this.volumeManager)) {
|
|
2645
|
+
yield event;
|
|
2646
|
+
if (event.type === "exit") {
|
|
2647
|
+
setupExitCode = Number(event.data);
|
|
2489
2648
|
}
|
|
2490
2649
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
let cmd;
|
|
2494
|
-
if (request.stdin) {
|
|
2495
|
-
const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
|
|
2496
|
-
await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
|
|
2497
|
-
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
2498
|
-
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2499
|
-
} else {
|
|
2500
|
-
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2650
|
+
if (setupExitCode !== 0) {
|
|
2651
|
+
return;
|
|
2501
2652
|
}
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
AttachStderr: true,
|
|
2507
|
-
WorkingDir: execWorkdir,
|
|
2508
|
-
User: "sandbox"
|
|
2509
|
-
});
|
|
2510
|
-
const execStream = await exec.start({ Tty: false });
|
|
2511
|
-
yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
|
|
2512
|
-
} finally {
|
|
2513
|
-
if (this.persist) {
|
|
2514
|
-
logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
|
|
2515
|
-
} else {
|
|
2516
|
-
try {
|
|
2517
|
-
await container.remove({ force: true });
|
|
2518
|
-
} catch {}
|
|
2653
|
+
}
|
|
2654
|
+
if (request.files) {
|
|
2655
|
+
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
2656
|
+
await this.volumeManager.writeFileViaExec(container, fPath, fContent);
|
|
2519
2657
|
}
|
|
2520
2658
|
}
|
|
2659
|
+
let rawCmd;
|
|
2660
|
+
if (request.cmd) {
|
|
2661
|
+
rawCmd = ["bash", "-c", request.cmd];
|
|
2662
|
+
} else {
|
|
2663
|
+
const ext = request.fileExtension ?? adapter.getFileExtension();
|
|
2664
|
+
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2665
|
+
await this.volumeManager.writeFileViaExec(container, filePath, request.code);
|
|
2666
|
+
rawCmd = this.buildAdapterCommand(adapter, request, filePath);
|
|
2667
|
+
}
|
|
2668
|
+
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2669
|
+
let cmd;
|
|
2670
|
+
if (request.stdin) {
|
|
2671
|
+
const stdinPath = `${SANDBOX_WORKDIR}/_stdin`;
|
|
2672
|
+
await this.volumeManager.writeFileViaExec(container, stdinPath, request.stdin);
|
|
2673
|
+
const cmdStr = rawCmd.map((a) => `'${a.replace(/'/g, "'\\''")}'`).join(" ");
|
|
2674
|
+
cmd = this.executionManager.wrapWithTimeout(["sh", "-c", `cat ${stdinPath} | ${cmdStr}`], timeoutSec);
|
|
2675
|
+
} else {
|
|
2676
|
+
cmd = this.executionManager.wrapWithTimeout(rawCmd, timeoutSec);
|
|
2677
|
+
}
|
|
2678
|
+
const exec = await container.exec({
|
|
2679
|
+
Cmd: cmd,
|
|
2680
|
+
Env: this.executionManager.buildEnv(request.env, this.networkManager.proxyPort, this.network, this.networkFilter),
|
|
2681
|
+
AttachStdout: true,
|
|
2682
|
+
AttachStderr: true,
|
|
2683
|
+
WorkingDir: execWorkdir,
|
|
2684
|
+
User: "sandbox"
|
|
2685
|
+
});
|
|
2686
|
+
const execStream = await exec.start({ Tty: false });
|
|
2687
|
+
yield* this.executionManager.streamExecOutput(execStream, exec, container, timeoutMs);
|
|
2521
2688
|
} finally {
|
|
2522
|
-
this.
|
|
2689
|
+
if (this.persist) {
|
|
2690
|
+
logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
|
|
2691
|
+
} else {
|
|
2692
|
+
pool.release(container, image).catch((err) => {
|
|
2693
|
+
logger.debug(`[Pool] release failed: ${err}`);
|
|
2694
|
+
container.remove({ force: true }).catch(() => {});
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2523
2697
|
}
|
|
2524
2698
|
}
|
|
2525
2699
|
async resolveImage(adapter, requestedPackages) {
|
|
@@ -2646,31 +2820,59 @@ class DockerIsol8 {
|
|
|
2646
2820
|
try {
|
|
2647
2821
|
await this.networkManager.startProxy(container);
|
|
2648
2822
|
await this.networkManager.setupIptables(container);
|
|
2649
|
-
const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
|
|
2650
2823
|
let rawCmd;
|
|
2651
|
-
if (
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2824
|
+
if (req.cmd) {
|
|
2825
|
+
rawCmd = ["bash", "-c", req.cmd];
|
|
2826
|
+
} else {
|
|
2827
|
+
const canUseInline = !(req.stdin || req.files || req.outputPaths) && (!req.installPackages || req.installPackages.length === 0);
|
|
2828
|
+
if (canUseInline) {
|
|
2829
|
+
try {
|
|
2830
|
+
rawCmd = this.buildAdapterCommand(adapter, req);
|
|
2831
|
+
} catch {
|
|
2832
|
+
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2833
|
+
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2834
|
+
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2835
|
+
rawCmd = this.buildAdapterCommand(adapter, req, filePath);
|
|
2836
|
+
}
|
|
2837
|
+
} else {
|
|
2655
2838
|
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2656
2839
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2657
2840
|
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2658
|
-
rawCmd =
|
|
2841
|
+
rawCmd = this.buildAdapterCommand(adapter, req, filePath);
|
|
2659
2842
|
}
|
|
2660
|
-
} else {
|
|
2661
|
-
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2662
|
-
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
2663
|
-
await this.volumeManager.writeFileViaExec(container, filePath, req.code);
|
|
2664
|
-
rawCmd = adapter.getCommand(req.code, filePath);
|
|
2665
2843
|
}
|
|
2666
2844
|
if (resolved.remainingPackages.length > 0) {
|
|
2667
2845
|
await this.executionManager.installPackages(container, req.runtime, resolved.remainingPackages, timeoutMs);
|
|
2668
2846
|
}
|
|
2669
2847
|
if (resolved.imageSetupScript) {
|
|
2670
|
-
|
|
2848
|
+
let stderr2 = "";
|
|
2849
|
+
let exitCode = 0;
|
|
2850
|
+
for await (const event of this.executionManager.runSetupScript(container, resolved.imageSetupScript, timeoutMs, this.volumeManager)) {
|
|
2851
|
+
if (event.type === "stderr") {
|
|
2852
|
+
stderr2 += event.data;
|
|
2853
|
+
}
|
|
2854
|
+
if (event.type === "exit") {
|
|
2855
|
+
exitCode = Number(event.data);
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
if (exitCode !== 0) {
|
|
2859
|
+
throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
|
|
2860
|
+
}
|
|
2671
2861
|
}
|
|
2672
2862
|
if (req.setupScript) {
|
|
2673
|
-
|
|
2863
|
+
let stderr2 = "";
|
|
2864
|
+
let exitCode = 0;
|
|
2865
|
+
for await (const event of this.executionManager.runSetupScript(container, req.setupScript, timeoutMs, this.volumeManager)) {
|
|
2866
|
+
if (event.type === "stderr") {
|
|
2867
|
+
stderr2 += event.data;
|
|
2868
|
+
}
|
|
2869
|
+
if (event.type === "exit") {
|
|
2870
|
+
exitCode = Number(event.data);
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
if (exitCode !== 0) {
|
|
2874
|
+
throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
|
|
2875
|
+
}
|
|
2674
2876
|
}
|
|
2675
2877
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2676
2878
|
let cmd;
|
|
@@ -2762,24 +2964,53 @@ class DockerIsol8 {
|
|
|
2762
2964
|
} else if (this.persistentRuntime?.name !== adapter.name) {
|
|
2763
2965
|
throw new Error(`Cannot switch runtime from "${this.persistentRuntime?.name}" to "${adapter.name}". Each persistent container supports a single runtime. Create a new Isol8 instance for a different runtime.`);
|
|
2764
2966
|
}
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2967
|
+
let rawCmd;
|
|
2968
|
+
if (req.cmd) {
|
|
2969
|
+
rawCmd = ["bash", "-c", req.cmd];
|
|
2970
|
+
} else {
|
|
2971
|
+
const ext = req.fileExtension ?? adapter.getFileExtension();
|
|
2972
|
+
const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
|
|
2973
|
+
await this.volumeManager.putFile(this.container, filePath, req.code);
|
|
2974
|
+
rawCmd = this.buildAdapterCommand(adapter, req, filePath);
|
|
2975
|
+
}
|
|
2768
2976
|
if (req.files) {
|
|
2769
2977
|
for (const [fPath, fContent] of Object.entries(req.files)) {
|
|
2770
2978
|
await this.volumeManager.putFile(this.container, fPath, fContent);
|
|
2771
2979
|
}
|
|
2772
2980
|
}
|
|
2773
|
-
const rawCmd = adapter.getCommand(req.code, filePath);
|
|
2774
2981
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
2775
2982
|
if (remainingPackages.length > 0) {
|
|
2776
2983
|
await this.executionManager.installPackages(this.container, req.runtime, remainingPackages, timeoutMs);
|
|
2777
2984
|
}
|
|
2778
2985
|
if (imageSetupScript) {
|
|
2779
|
-
|
|
2986
|
+
let stderr2 = "";
|
|
2987
|
+
let exitCode = 0;
|
|
2988
|
+
for await (const event of this.executionManager.runSetupScript(this.container, imageSetupScript, timeoutMs, this.volumeManager)) {
|
|
2989
|
+
if (event.type === "stderr") {
|
|
2990
|
+
stderr2 += event.data;
|
|
2991
|
+
}
|
|
2992
|
+
if (event.type === "exit") {
|
|
2993
|
+
exitCode = Number(event.data);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
if (exitCode !== 0) {
|
|
2997
|
+
throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
|
|
2998
|
+
}
|
|
2780
2999
|
}
|
|
2781
3000
|
if (req.setupScript) {
|
|
2782
|
-
|
|
3001
|
+
let stderr2 = "";
|
|
3002
|
+
let exitCode = 0;
|
|
3003
|
+
for await (const event of this.executionManager.runSetupScript(this.container, req.setupScript, timeoutMs, this.volumeManager)) {
|
|
3004
|
+
if (event.type === "stderr") {
|
|
3005
|
+
stderr2 += event.data;
|
|
3006
|
+
}
|
|
3007
|
+
if (event.type === "exit") {
|
|
3008
|
+
exitCode = Number(event.data);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
if (exitCode !== 0) {
|
|
3012
|
+
throw new Error(`Setup script failed (exit code ${exitCode}): ${stderr2}`);
|
|
3013
|
+
}
|
|
2783
3014
|
}
|
|
2784
3015
|
let cmd;
|
|
2785
3016
|
if (req.stdin) {
|
|
@@ -2878,6 +3109,30 @@ class DockerIsol8 {
|
|
|
2878
3109
|
getAdapter(runtime) {
|
|
2879
3110
|
return RuntimeRegistry.get(runtime);
|
|
2880
3111
|
}
|
|
3112
|
+
validateAgentRuntime(req) {
|
|
3113
|
+
if (req.runtime !== "agent") {
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
if (this.network === "none") {
|
|
3117
|
+
throw new Error(`Agent runtime requires network access. The AI coding agent needs to reach its LLM provider API. Use --net host, or --net filtered --allow "api.anthropic.com" (or your provider's domain).`);
|
|
3118
|
+
}
|
|
3119
|
+
if (this.network === "filtered") {
|
|
3120
|
+
const whitelist = this.networkFilter?.whitelist ?? [];
|
|
3121
|
+
if (whitelist.length === 0) {
|
|
3122
|
+
throw new Error(`Agent runtime requires at least one network whitelist entry. The AI coding agent needs to reach its LLM provider API. Use --allow "api.anthropic.com" (or your provider's domain).`);
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
buildAdapterCommand(adapter, req, filePath) {
|
|
3127
|
+
const code = req.code ?? "";
|
|
3128
|
+
if (adapter.getCommandWithOptions) {
|
|
3129
|
+
return adapter.getCommandWithOptions(code, {
|
|
3130
|
+
filePath,
|
|
3131
|
+
agentFlags: req.agentFlags
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
return adapter.getCommand(code, filePath);
|
|
3135
|
+
}
|
|
2881
3136
|
buildHostConfig() {
|
|
2882
3137
|
const config = {
|
|
2883
3138
|
Memory: parseMemoryLimit(this.memoryLimit),
|
|
@@ -2986,7 +3241,7 @@ init_logger();
|
|
|
2986
3241
|
// package.json
|
|
2987
3242
|
var package_default = {
|
|
2988
3243
|
name: "@isol8/core",
|
|
2989
|
-
version: "0.
|
|
3244
|
+
version: "0.20.0",
|
|
2990
3245
|
description: "Sandboxed code execution engine for AI agents and apps (Docker, runtime and network controls)",
|
|
2991
3246
|
author: "Illusion47586",
|
|
2992
3247
|
license: "MIT",
|
|
@@ -3067,7 +3322,8 @@ export {
|
|
|
3067
3322
|
LABELS,
|
|
3068
3323
|
DockerIsol8,
|
|
3069
3324
|
DenoAdapter,
|
|
3070
|
-
BunAdapter
|
|
3325
|
+
BunAdapter,
|
|
3326
|
+
AgentAdapter
|
|
3071
3327
|
};
|
|
3072
3328
|
|
|
3073
|
-
//# debugId=
|
|
3329
|
+
//# debugId=767B2279906CCA3764756E2164756E21
|