isol8 0.11.1 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/dist/cli.js +344 -25
- package/dist/index.js +177 -18
- package/dist/src/engine/default-seccomp-profile.d.ts +8 -0
- package/dist/src/engine/default-seccomp-profile.d.ts.map +1 -0
- package/dist/src/engine/docker.d.ts.map +1 -1
- package/dist/src/server/index.d.ts +20 -0
- package/dist/src/server/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ isol8 run script.py --host http://server:3000 --key my-api-key
|
|
|
105
105
|
|------|-------------|---------|
|
|
106
106
|
| `-e, --eval <code>` | Execute inline code | — |
|
|
107
107
|
| `-r, --runtime <rt>` | Force runtime: `python`, `node`, `bun`, `deno`, `bash` | auto-detect |
|
|
108
|
-
| `--net <mode>` | Network mode: `none`, `host`, `filtered` | `none` |
|
|
108
|
+
| `--net <mode>` | Network mode: `none`, `host`, `filtered` | `none` (unless `--install` is used without explicit `--net`, then auto `filtered`) |
|
|
109
109
|
| `--allow <regex>` | Whitelist regex (repeatable, for `filtered`) | — |
|
|
110
110
|
| `--deny <regex>` | Blacklist regex (repeatable, for `filtered`) | — |
|
|
111
111
|
| `--out <file>` | Write stdout to file | — |
|
|
@@ -113,7 +113,7 @@ isol8 run script.py --host http://server:3000 --key my-api-key
|
|
|
113
113
|
| `--persistent` | Keep container alive between runs | `false` |
|
|
114
114
|
| `--persist` | Keep container after execution for inspection/debugging | `false` |
|
|
115
115
|
| `--debug` | Enable debug logging for internal engine operations | `false` |
|
|
116
|
-
| `--timeout <ms>` |
|
|
116
|
+
| `--timeout <ms>` | Timeout in milliseconds for package install + execution phases | `30000` |
|
|
117
117
|
| `--memory <limit>` | Memory limit (e.g. `512m`, `1g`) | `512m` |
|
|
118
118
|
| `--cpu <limit>` | CPU limit as fraction (e.g. `0.5`, `2.0`) | `1.0` |
|
|
119
119
|
| `--image <name>` | Override Docker image | — |
|
|
@@ -124,7 +124,7 @@ isol8 run script.py --host http://server:3000 --key my-api-key
|
|
|
124
124
|
| `--sandbox-size <size>` | Sandbox tmpfs size (e.g. `512m`, `1g`) | `512m` |
|
|
125
125
|
| `--tmp-size <size>` | Tmp tmpfs size (e.g. `256m`, `512m`) | `256m` |
|
|
126
126
|
| `--stdin <data>` | Data to pipe to stdin | — |
|
|
127
|
-
| `--install <pkg>` | Install package for runtime (repeatable) | — |
|
|
127
|
+
| `--install <pkg>` | Install package for runtime (repeatable) | — (auto-adds default runtime registry allowlist in `filtered` mode) |
|
|
128
128
|
| `--url <url>` | Fetch code from URL (requires `remoteCode.enabled=true`) | — |
|
|
129
129
|
| `--github <owner/repo/ref/path>` | GitHub shorthand for raw source | — |
|
|
130
130
|
| `--gist <gistId/file.ext>` | Gist shorthand for raw source | — |
|
|
@@ -169,11 +169,15 @@ isol8 serve --update # Force re-download the server binary
|
|
|
169
169
|
|
|
170
170
|
| Flag | Description | Default |
|
|
171
171
|
|------|-------------|---------|
|
|
172
|
-
| `-p, --port <port>` | Port to listen on | `3000` |
|
|
172
|
+
| `-p, --port <port>` | Port to listen on | `--port` > `$ISOL8_PORT` > `$PORT` > `3000` |
|
|
173
173
|
| `-k, --key <key>` | API key for Bearer token auth | `$ISOL8_API_KEY` |
|
|
174
174
|
| `--update` | Force re-download the server binary | `false` |
|
|
175
175
|
| `--debug` | Enable debug logging for server operations | `false` |
|
|
176
176
|
|
|
177
|
+
If the selected port is already in use, `isol8 serve` now prompts to enter another port or auto-select an available one. In non-interactive environments, it auto-falls back to a free port.
|
|
178
|
+
|
|
179
|
+
On graceful shutdown (`SIGINT`/`SIGTERM`), the server now cleans up tracked sessions, isol8 containers, and isol8 images before exiting.
|
|
180
|
+
|
|
177
181
|
### `isol8 config`
|
|
178
182
|
|
|
179
183
|
Display the resolved configuration (merged defaults + config file). Shows the source file, defaults, network rules, cleanup policy, and dependencies.
|
|
@@ -375,7 +379,7 @@ Add the `$schema` property to get autocompletion, validation, and inline documen
|
|
|
375
379
|
"node": ["lodash"]
|
|
376
380
|
},
|
|
377
381
|
"security": {
|
|
378
|
-
"seccomp": "
|
|
382
|
+
"seccomp": "strict"
|
|
379
383
|
}
|
|
380
384
|
}
|
|
381
385
|
```
|
|
@@ -439,7 +443,7 @@ bun run bench:detailed # Phase breakdown
|
|
|
439
443
|
| **Network** | Disabled by default; optional proxy-based filtering |
|
|
440
444
|
| **Output** | Truncated at 1MB; secrets masked from stdout/stderr |
|
|
441
445
|
| **Isolation** | Each execution in its own container (ephemeral) or exec (persistent) |
|
|
442
|
-
| **Seccomp** | Default
|
|
446
|
+
| **Seccomp** | Default `strict` mode applies the built-in profile that blocks dangerous syscalls (mount, swap, ptrace). In standalone server binaries, an embedded copy is used when profile files are not present. If strict/custom profile loading fails, execution fails. |
|
|
443
447
|
|
|
444
448
|
### Container Filesystem
|
|
445
449
|
|
|
@@ -468,6 +472,7 @@ When running `isol8 serve`, these endpoints are available:
|
|
|
468
472
|
| `POST` | `/file` | Upload file (base64) |
|
|
469
473
|
| `GET` | `/file?sessionId=&path=` | Download file (base64) |
|
|
470
474
|
| `DELETE` | `/session/:id` | Destroy persistent session |
|
|
475
|
+
| `POST` | `/cleanup` | Run server-side cleanup for sessions/containers (and images by default) |
|
|
471
476
|
|
|
472
477
|
All endpoints (except `/health`) require `Authorization: Bearer <key>`.
|
|
473
478
|
|
package/dist/cli.js
CHANGED
|
@@ -6318,7 +6318,7 @@ var require_bcrypt_pbkdf = __commonJS((exports, module) => {
|
|
|
6318
6318
|
|
|
6319
6319
|
// node_modules/cpu-features/build/Release/cpufeatures.node
|
|
6320
6320
|
var require_cpufeatures = __commonJS((exports, module) => {
|
|
6321
|
-
module.exports = __require("./cpufeatures-
|
|
6321
|
+
module.exports = __require("./cpufeatures-1yrn0vtw.node");
|
|
6322
6322
|
});
|
|
6323
6323
|
|
|
6324
6324
|
// node_modules/cpu-features/lib/index.js
|
|
@@ -55412,6 +55412,73 @@ class Semaphore {
|
|
|
55412
55412
|
}
|
|
55413
55413
|
}
|
|
55414
55414
|
|
|
55415
|
+
// src/engine/default-seccomp-profile.ts
|
|
55416
|
+
var EMBEDDED_DEFAULT_SECCOMP_PROFILE;
|
|
55417
|
+
var init_default_seccomp_profile = __esm(() => {
|
|
55418
|
+
EMBEDDED_DEFAULT_SECCOMP_PROFILE = JSON.stringify({
|
|
55419
|
+
defaultAction: "SCMP_ACT_ALLOW",
|
|
55420
|
+
architectures: ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32", "SCMP_ARCH_AARCH64"],
|
|
55421
|
+
syscalls: [
|
|
55422
|
+
{
|
|
55423
|
+
names: [
|
|
55424
|
+
"acct",
|
|
55425
|
+
"add_key",
|
|
55426
|
+
"bpf",
|
|
55427
|
+
"clock_adjtime",
|
|
55428
|
+
"clock_settime",
|
|
55429
|
+
"create_module",
|
|
55430
|
+
"delete_module",
|
|
55431
|
+
"finit_module",
|
|
55432
|
+
"get_mempolicy",
|
|
55433
|
+
"init_module",
|
|
55434
|
+
"ioperm",
|
|
55435
|
+
"iopl",
|
|
55436
|
+
"kcmp",
|
|
55437
|
+
"kexec_file_load",
|
|
55438
|
+
"kexec_load",
|
|
55439
|
+
"keyctl",
|
|
55440
|
+
"lookup_dcookie",
|
|
55441
|
+
"mbind",
|
|
55442
|
+
"mount",
|
|
55443
|
+
"move_pages",
|
|
55444
|
+
"name_to_handle_at",
|
|
55445
|
+
"open_by_handle_at",
|
|
55446
|
+
"perf_event_open",
|
|
55447
|
+
"pivot_root",
|
|
55448
|
+
"process_vm_readv",
|
|
55449
|
+
"process_vm_writev",
|
|
55450
|
+
"ptrace",
|
|
55451
|
+
"query_module",
|
|
55452
|
+
"quotactl",
|
|
55453
|
+
"reboot",
|
|
55454
|
+
"request_key",
|
|
55455
|
+
"set_mempolicy",
|
|
55456
|
+
"setns",
|
|
55457
|
+
"settimeofday",
|
|
55458
|
+
"stime",
|
|
55459
|
+
"swapon",
|
|
55460
|
+
"swapoff",
|
|
55461
|
+
"sysfs",
|
|
55462
|
+
"syslog",
|
|
55463
|
+
"umount",
|
|
55464
|
+
"umount2",
|
|
55465
|
+
"unshare",
|
|
55466
|
+
"uselib",
|
|
55467
|
+
"userfaultfd",
|
|
55468
|
+
"ustat",
|
|
55469
|
+
"vm86",
|
|
55470
|
+
"vm86old"
|
|
55471
|
+
],
|
|
55472
|
+
action: "SCMP_ACT_ERRNO",
|
|
55473
|
+
args: [],
|
|
55474
|
+
comment: "",
|
|
55475
|
+
includes: {},
|
|
55476
|
+
excludes: {}
|
|
55477
|
+
}
|
|
55478
|
+
]
|
|
55479
|
+
});
|
|
55480
|
+
});
|
|
55481
|
+
|
|
55415
55482
|
// src/engine/utils.ts
|
|
55416
55483
|
var exports_utils = {};
|
|
55417
55484
|
__export(exports_utils, {
|
|
@@ -56155,7 +56222,19 @@ function wrapWithTimeout(cmd, timeoutSec) {
|
|
|
56155
56222
|
function getInstallCommand(runtime, packages) {
|
|
56156
56223
|
switch (runtime) {
|
|
56157
56224
|
case "python":
|
|
56158
|
-
return [
|
|
56225
|
+
return [
|
|
56226
|
+
"pip",
|
|
56227
|
+
"install",
|
|
56228
|
+
"--user",
|
|
56229
|
+
"--no-cache-dir",
|
|
56230
|
+
"--break-system-packages",
|
|
56231
|
+
"--disable-pip-version-check",
|
|
56232
|
+
"--retries",
|
|
56233
|
+
"0",
|
|
56234
|
+
"--timeout",
|
|
56235
|
+
"15",
|
|
56236
|
+
...packages
|
|
56237
|
+
];
|
|
56159
56238
|
case "node":
|
|
56160
56239
|
return ["npm", "install", "--prefix", "/sandbox", ...packages];
|
|
56161
56240
|
case "bun":
|
|
@@ -56168,8 +56247,9 @@ function getInstallCommand(runtime, packages) {
|
|
|
56168
56247
|
throw new Error(`Unknown runtime for package install: ${runtime}`);
|
|
56169
56248
|
}
|
|
56170
56249
|
}
|
|
56171
|
-
async function installPackages(container, runtime, packages) {
|
|
56172
|
-
const
|
|
56250
|
+
async function installPackages(container, runtime, packages, timeoutMs) {
|
|
56251
|
+
const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
56252
|
+
const cmd = wrapWithTimeout(getInstallCommand(runtime, packages), timeoutSec);
|
|
56173
56253
|
logger.debug(`Installing packages: ${JSON.stringify(cmd)}`);
|
|
56174
56254
|
const env2 = [
|
|
56175
56255
|
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
|
|
@@ -56180,6 +56260,12 @@ async function installPackages(container, runtime, packages) {
|
|
|
56180
56260
|
env2.push("NPM_CONFIG_PREFIX=/sandbox/.npm-global");
|
|
56181
56261
|
env2.push("NPM_CONFIG_CACHE=/sandbox/.npm-cache");
|
|
56182
56262
|
env2.push("npm_config_cache=/sandbox/.npm-cache");
|
|
56263
|
+
env2.push("NPM_CONFIG_FETCH_RETRIES=0");
|
|
56264
|
+
env2.push("npm_config_fetch_retries=0");
|
|
56265
|
+
env2.push("NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=1000");
|
|
56266
|
+
env2.push("npm_config_fetch_retry_mintimeout=1000");
|
|
56267
|
+
env2.push("NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=2000");
|
|
56268
|
+
env2.push("npm_config_fetch_retry_maxtimeout=2000");
|
|
56183
56269
|
} else if (runtime === "bun") {
|
|
56184
56270
|
env2.push("BUN_INSTALL_GLOBAL_DIR=/sandbox/.bun-global");
|
|
56185
56271
|
env2.push("BUN_INSTALL_CACHE_DIR=/sandbox/.bun-cache");
|
|
@@ -56201,7 +56287,13 @@ async function installPackages(container, runtime, packages) {
|
|
|
56201
56287
|
const stderrStream = new PassThrough;
|
|
56202
56288
|
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
56203
56289
|
stderrStream.on("data", (chunk) => {
|
|
56204
|
-
|
|
56290
|
+
const text = chunk.toString();
|
|
56291
|
+
stderr += text;
|
|
56292
|
+
logger.debug(`[install:${runtime}:stderr] ${text.trimEnd()}`);
|
|
56293
|
+
});
|
|
56294
|
+
stdoutStream.on("data", (chunk) => {
|
|
56295
|
+
const text = chunk.toString();
|
|
56296
|
+
logger.debug(`[install:${runtime}:stdout] ${text.trimEnd()}`);
|
|
56205
56297
|
});
|
|
56206
56298
|
stream.on("end", async () => {
|
|
56207
56299
|
try {
|
|
@@ -56531,7 +56623,7 @@ class DockerIsol8 {
|
|
|
56531
56623
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
56532
56624
|
await writeFileViaExec(container, filePath, request.code);
|
|
56533
56625
|
if (request.installPackages?.length) {
|
|
56534
|
-
await installPackages(container, request.runtime, request.installPackages);
|
|
56626
|
+
await installPackages(container, request.runtime, request.installPackages, timeoutMs);
|
|
56535
56627
|
}
|
|
56536
56628
|
if (request.files) {
|
|
56537
56629
|
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
@@ -56660,7 +56752,7 @@ class DockerIsol8 {
|
|
|
56660
56752
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
56661
56753
|
}
|
|
56662
56754
|
if (req.installPackages?.length) {
|
|
56663
|
-
await installPackages(container, req.runtime, req.installPackages);
|
|
56755
|
+
await installPackages(container, req.runtime, req.installPackages, timeoutMs);
|
|
56664
56756
|
}
|
|
56665
56757
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
56666
56758
|
let cmd;
|
|
@@ -56768,7 +56860,7 @@ class DockerIsol8 {
|
|
|
56768
56860
|
const rawCmd = adapter.getCommand(req.code, filePath);
|
|
56769
56861
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
56770
56862
|
if (req.installPackages?.length) {
|
|
56771
|
-
await installPackages(this.container, req.runtime, req.installPackages);
|
|
56863
|
+
await installPackages(this.container, req.runtime, req.installPackages, timeoutMs);
|
|
56772
56864
|
}
|
|
56773
56865
|
let cmd;
|
|
56774
56866
|
if (req.stdin) {
|
|
@@ -56911,17 +57003,15 @@ class DockerIsol8 {
|
|
|
56911
57003
|
const profile = readFileSync3(this.security.customProfilePath, "utf-8");
|
|
56912
57004
|
opts.push(`seccomp=${profile}`);
|
|
56913
57005
|
} catch (e) {
|
|
56914
|
-
|
|
57006
|
+
throw new Error(`Failed to load custom seccomp profile at ${this.security.customProfilePath}: ${e}`);
|
|
56915
57007
|
}
|
|
56916
57008
|
return opts;
|
|
56917
57009
|
}
|
|
56918
57010
|
try {
|
|
56919
57011
|
const profile = this.loadDefaultSeccompProfile();
|
|
56920
|
-
|
|
56921
|
-
opts.push(`seccomp=${profile}`);
|
|
56922
|
-
}
|
|
57012
|
+
opts.push(`seccomp=${profile}`);
|
|
56923
57013
|
} catch (e) {
|
|
56924
|
-
|
|
57014
|
+
throw new Error(`Failed to load default seccomp profile: ${e}`);
|
|
56925
57015
|
}
|
|
56926
57016
|
return opts;
|
|
56927
57017
|
}
|
|
@@ -56934,8 +57024,11 @@ class DockerIsol8 {
|
|
|
56934
57024
|
if (existsSync4(prodPath)) {
|
|
56935
57025
|
return readFileSync3(prodPath, "utf-8");
|
|
56936
57026
|
}
|
|
56937
|
-
|
|
56938
|
-
|
|
57027
|
+
if (EMBEDDED_DEFAULT_SECCOMP_PROFILE.length > 0) {
|
|
57028
|
+
logger.debug(`Default seccomp profile file not found. Using embedded profile. Tried: ${devPath.pathname}, ${prodPath.pathname}`);
|
|
57029
|
+
return EMBEDDED_DEFAULT_SECCOMP_PROFILE;
|
|
57030
|
+
}
|
|
57031
|
+
throw new Error("Embedded default seccomp profile is unavailable");
|
|
56939
57032
|
}
|
|
56940
57033
|
buildEnv(extra) {
|
|
56941
57034
|
const env2 = [
|
|
@@ -57160,6 +57253,7 @@ var init_docker = __esm(() => {
|
|
|
57160
57253
|
init_logger();
|
|
57161
57254
|
init_audit();
|
|
57162
57255
|
init_code_fetcher();
|
|
57256
|
+
init_default_seccomp_profile();
|
|
57163
57257
|
init_image_builder();
|
|
57164
57258
|
init_pool();
|
|
57165
57259
|
import_dockerode = __toESM(require_docker(), 1);
|
|
@@ -57171,7 +57265,7 @@ var package_default;
|
|
|
57171
57265
|
var init_package = __esm(() => {
|
|
57172
57266
|
package_default = {
|
|
57173
57267
|
name: "isol8",
|
|
57174
|
-
version: "0.11.
|
|
57268
|
+
version: "0.11.2",
|
|
57175
57269
|
description: "Secure code execution engine for AI agents",
|
|
57176
57270
|
author: "Illusion47586",
|
|
57177
57271
|
license: "MIT",
|
|
@@ -58919,6 +59013,50 @@ async function createServer(options) {
|
|
|
58919
59013
|
logger.debug(`[Server] Auto-prune: ${config.cleanup.autoPrune}`);
|
|
58920
59014
|
const app = new Hono2;
|
|
58921
59015
|
const globalSemaphore = new Semaphore(config.maxConcurrent);
|
|
59016
|
+
let pruneInterval;
|
|
59017
|
+
let cleanupInFlight = null;
|
|
59018
|
+
const cleanupSessions = async () => {
|
|
59019
|
+
let removed = 0;
|
|
59020
|
+
let failed = 0;
|
|
59021
|
+
const errors = [];
|
|
59022
|
+
for (const [id, session] of sessions) {
|
|
59023
|
+
try {
|
|
59024
|
+
await session.engine.stop();
|
|
59025
|
+
removed++;
|
|
59026
|
+
} catch (err) {
|
|
59027
|
+
failed++;
|
|
59028
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
59029
|
+
errors.push(`${id}: ${errorMsg}`);
|
|
59030
|
+
} finally {
|
|
59031
|
+
sessions.delete(id);
|
|
59032
|
+
}
|
|
59033
|
+
}
|
|
59034
|
+
return { removed, failed, errors };
|
|
59035
|
+
};
|
|
59036
|
+
const runCleanup = async (includeImages) => {
|
|
59037
|
+
if (cleanupInFlight) {
|
|
59038
|
+
return cleanupInFlight;
|
|
59039
|
+
}
|
|
59040
|
+
cleanupInFlight = (async () => {
|
|
59041
|
+
logger.info(`[Server] Starting cleanup (sessions=true containers=true images=${includeImages})`);
|
|
59042
|
+
const sessionsResult = await cleanupSessions();
|
|
59043
|
+
const containersResult = await DockerIsol82.cleanup();
|
|
59044
|
+
const result = {
|
|
59045
|
+
sessions: sessionsResult,
|
|
59046
|
+
containers: containersResult
|
|
59047
|
+
};
|
|
59048
|
+
if (includeImages) {
|
|
59049
|
+
result.images = await DockerIsol82.cleanupImages();
|
|
59050
|
+
}
|
|
59051
|
+
logger.info(`[Server] Cleanup complete: sessions=${result.sessions.removed}/${result.sessions.failed} containers=${result.containers.removed}/${result.containers.failed}${result.images ? ` images=${result.images.removed}/${result.images.failed}` : ""}`);
|
|
59052
|
+
return result;
|
|
59053
|
+
})();
|
|
59054
|
+
try {
|
|
59055
|
+
return await cleanupInFlight;
|
|
59056
|
+
} finally {
|
|
59057
|
+
cleanupInFlight = null;
|
|
59058
|
+
}
|
|
59059
|
+
};
|
|
58922
59060
|
app.use("*", authMiddleware(options.apiKey));
|
|
58923
59061
|
app.get("/health", (c) => c.json({ status: "ok", version: VERSION }));
|
|
58924
59062
|
app.post("/execute", async (c) => {
|
|
@@ -59099,8 +59237,21 @@ async function createServer(options) {
|
|
|
59099
59237
|
}
|
|
59100
59238
|
return c.json({ ok: true });
|
|
59101
59239
|
});
|
|
59240
|
+
app.post("/cleanup", async (c) => {
|
|
59241
|
+
const body = await c.req.json().catch(() => ({}));
|
|
59242
|
+
const includeImages = body.images ?? true;
|
|
59243
|
+
logger.debug(`[Server] POST /cleanup images=${includeImages}`);
|
|
59244
|
+
try {
|
|
59245
|
+
const result = await runCleanup(includeImages);
|
|
59246
|
+
return c.json({ ok: true, ...result });
|
|
59247
|
+
} catch (err) {
|
|
59248
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59249
|
+
logger.error(`[Server] Cleanup failed: ${message}`);
|
|
59250
|
+
return c.json({ error: message }, 500);
|
|
59251
|
+
}
|
|
59252
|
+
});
|
|
59102
59253
|
if (config.cleanup.autoPrune) {
|
|
59103
|
-
setInterval(async () => {
|
|
59254
|
+
pruneInterval = setInterval(async () => {
|
|
59104
59255
|
const maxAge = config.cleanup.maxContainerAgeMs;
|
|
59105
59256
|
const now = Date.now();
|
|
59106
59257
|
for (const [id, session] of sessions) {
|
|
@@ -59118,7 +59269,15 @@ async function createServer(options) {
|
|
|
59118
59269
|
return {
|
|
59119
59270
|
app,
|
|
59120
59271
|
fetch: app.fetch,
|
|
59121
|
-
port: options.port
|
|
59272
|
+
port: options.port,
|
|
59273
|
+
cleanup: async (includeImages = true) => runCleanup(includeImages),
|
|
59274
|
+
shutdown: async (includeImages = true) => {
|
|
59275
|
+
if (pruneInterval) {
|
|
59276
|
+
clearInterval(pruneInterval);
|
|
59277
|
+
pruneInterval = undefined;
|
|
59278
|
+
}
|
|
59279
|
+
await runCleanup(includeImages);
|
|
59280
|
+
}
|
|
59122
59281
|
};
|
|
59123
59282
|
}
|
|
59124
59283
|
var sessions;
|
|
@@ -62747,22 +62906,54 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
|
|
|
62747
62906
|
process.exit(exitCode);
|
|
62748
62907
|
}
|
|
62749
62908
|
});
|
|
62750
|
-
program2.command("serve").description("Start the isol8 remote server").option("-p, --port <port>", "Port to listen on"
|
|
62909
|
+
program2.command("serve").description("Start the isol8 remote server").option("-p, --port <port>", "Port to listen on").option("-k, --key <key>", "API key for authentication").option("--update", "Force re-download the server binary").option("--debug", "Enable debug logging").action(async (opts) => {
|
|
62751
62910
|
const apiKey = opts.key ?? process.env.ISOL8_API_KEY;
|
|
62752
62911
|
if (!apiKey) {
|
|
62753
62912
|
console.error("[ERR] API key required. Use --key or ISOL8_API_KEY env var.");
|
|
62754
62913
|
process.exit(1);
|
|
62755
62914
|
}
|
|
62756
|
-
const
|
|
62757
|
-
|
|
62915
|
+
const requestedPort = resolveServePort(opts.port);
|
|
62916
|
+
const port = await resolveAvailableServePort(requestedPort);
|
|
62917
|
+
logger.debug(`[Serve] Requested port: ${requestedPort}`);
|
|
62918
|
+
logger.debug(`[Serve] Using port: ${port}`);
|
|
62758
62919
|
logger.debug(`[Serve] API key: ${"*".repeat(apiKey.length)}`);
|
|
62759
62920
|
if (typeof globalThis.Bun !== "undefined") {
|
|
62760
62921
|
logger.debug("[Serve] Running under Bun, starting server in-process");
|
|
62761
62922
|
const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
|
|
62762
62923
|
const server = await createServer2({ port, apiKey, debug: opts.debug ?? false });
|
|
62924
|
+
let shuttingDown = false;
|
|
62925
|
+
const bunServer = Bun.serve({ fetch: server.app.fetch, port });
|
|
62926
|
+
const shutdown = async () => {
|
|
62927
|
+
if (shuttingDown) {
|
|
62928
|
+
return;
|
|
62929
|
+
}
|
|
62930
|
+
shuttingDown = true;
|
|
62931
|
+
logger.info("[Serve] Shutting down server and cleaning up resources...");
|
|
62932
|
+
bunServer.stop();
|
|
62933
|
+
try {
|
|
62934
|
+
await server.shutdown();
|
|
62935
|
+
logger.info("[Serve] Cleanup complete");
|
|
62936
|
+
process.exit(0);
|
|
62937
|
+
} catch (err) {
|
|
62938
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62939
|
+
logger.error(`[Serve] Cleanup failed: ${message}`);
|
|
62940
|
+
process.exit(1);
|
|
62941
|
+
}
|
|
62942
|
+
};
|
|
62943
|
+
process.on("SIGINT", () => {
|
|
62944
|
+
shutdown().catch((err) => {
|
|
62945
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62946
|
+
logger.error(`[Serve] Shutdown handler failed: ${message}`);
|
|
62947
|
+
});
|
|
62948
|
+
});
|
|
62949
|
+
process.on("SIGTERM", () => {
|
|
62950
|
+
shutdown().catch((err) => {
|
|
62951
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62952
|
+
logger.error(`[Serve] Shutdown handler failed: ${message}`);
|
|
62953
|
+
});
|
|
62954
|
+
});
|
|
62763
62955
|
console.log(`[INFO] isol8 server v${VERSION} listening on http://localhost:${port}`);
|
|
62764
62956
|
console.log(" Auth: Bearer token required");
|
|
62765
|
-
Bun.serve({ fetch: server.app.fetch, port });
|
|
62766
62957
|
return;
|
|
62767
62958
|
}
|
|
62768
62959
|
logger.debug("[Serve] Running under Node.js, launching standalone binary");
|
|
@@ -62861,6 +63052,11 @@ async function downloadServerBinary(binaryPath) {
|
|
|
62861
63052
|
}
|
|
62862
63053
|
}
|
|
62863
63054
|
async function promptYesNo(question) {
|
|
63055
|
+
const answer = await promptText(question);
|
|
63056
|
+
const normalized = answer.trim().toLowerCase();
|
|
63057
|
+
return normalized === "" || normalized === "y" || normalized === "yes";
|
|
63058
|
+
}
|
|
63059
|
+
async function promptText(question) {
|
|
62864
63060
|
const readline = await import("node:readline");
|
|
62865
63061
|
const rl = readline.createInterface({
|
|
62866
63062
|
input: process.stdin,
|
|
@@ -62870,8 +63066,103 @@ async function promptYesNo(question) {
|
|
|
62870
63066
|
rl.question(question, resolve3);
|
|
62871
63067
|
});
|
|
62872
63068
|
rl.close();
|
|
62873
|
-
|
|
62874
|
-
|
|
63069
|
+
return answer;
|
|
63070
|
+
}
|
|
63071
|
+
function parsePort(raw2, source) {
|
|
63072
|
+
const parsed = Number(raw2);
|
|
63073
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
63074
|
+
console.error(`[ERR] Invalid port from ${source}: ${raw2}. Expected 1-65535.`);
|
|
63075
|
+
process.exit(1);
|
|
63076
|
+
}
|
|
63077
|
+
return parsed;
|
|
63078
|
+
}
|
|
63079
|
+
function resolveServePort(portFlag) {
|
|
63080
|
+
if (typeof portFlag === "string") {
|
|
63081
|
+
return parsePort(portFlag, "--port");
|
|
63082
|
+
}
|
|
63083
|
+
if (process.env.ISOL8_PORT) {
|
|
63084
|
+
return parsePort(process.env.ISOL8_PORT, "ISOL8_PORT");
|
|
63085
|
+
}
|
|
63086
|
+
if (process.env.PORT) {
|
|
63087
|
+
return parsePort(process.env.PORT, "PORT");
|
|
63088
|
+
}
|
|
63089
|
+
return 3000;
|
|
63090
|
+
}
|
|
63091
|
+
async function isPortAvailable(port) {
|
|
63092
|
+
const { createServer: createServer2 } = await import("node:net");
|
|
63093
|
+
return await new Promise((resolve3) => {
|
|
63094
|
+
const server = createServer2();
|
|
63095
|
+
server.once("error", () => {
|
|
63096
|
+
resolve3(false);
|
|
63097
|
+
});
|
|
63098
|
+
server.once("listening", () => {
|
|
63099
|
+
server.close(() => resolve3(true));
|
|
63100
|
+
});
|
|
63101
|
+
server.listen(port);
|
|
63102
|
+
});
|
|
63103
|
+
}
|
|
63104
|
+
async function findAvailablePort() {
|
|
63105
|
+
const { createServer: createServer2 } = await import("node:net");
|
|
63106
|
+
return await new Promise((resolve3, reject) => {
|
|
63107
|
+
const server = createServer2();
|
|
63108
|
+
server.once("error", reject);
|
|
63109
|
+
server.once("listening", () => {
|
|
63110
|
+
const address = server.address();
|
|
63111
|
+
if (!address || typeof address === "string") {
|
|
63112
|
+
server.close(() => reject(new Error("Failed to determine available port")));
|
|
63113
|
+
return;
|
|
63114
|
+
}
|
|
63115
|
+
server.close((closeErr) => {
|
|
63116
|
+
if (closeErr) {
|
|
63117
|
+
reject(closeErr);
|
|
63118
|
+
return;
|
|
63119
|
+
}
|
|
63120
|
+
resolve3(address.port);
|
|
63121
|
+
});
|
|
63122
|
+
});
|
|
63123
|
+
server.listen(0);
|
|
63124
|
+
});
|
|
63125
|
+
}
|
|
63126
|
+
async function resolveAvailableServePort(port) {
|
|
63127
|
+
if (await isPortAvailable(port)) {
|
|
63128
|
+
return port;
|
|
63129
|
+
}
|
|
63130
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
63131
|
+
const autoPort = await findAvailablePort();
|
|
63132
|
+
console.warn(`[WARN] Port ${port} is in use. Falling back to available port ${autoPort}.`);
|
|
63133
|
+
return autoPort;
|
|
63134
|
+
}
|
|
63135
|
+
let candidate = port;
|
|
63136
|
+
while (true) {
|
|
63137
|
+
console.warn(`[WARN] Port ${candidate} is already in use.`);
|
|
63138
|
+
const choice = (await promptText("Choose: [1] Enter another port [2] Find an available port [3] Exit (default: 2): ")).trim().toLowerCase();
|
|
63139
|
+
if (choice === "" || choice === "2") {
|
|
63140
|
+
const autoPort = await findAvailablePort();
|
|
63141
|
+
console.log(`[INFO] Using available port ${autoPort}`);
|
|
63142
|
+
return autoPort;
|
|
63143
|
+
}
|
|
63144
|
+
if (choice === "1") {
|
|
63145
|
+
const rawPort = (await promptText("Enter port (1-65535): ")).trim();
|
|
63146
|
+
if (!rawPort) {
|
|
63147
|
+
continue;
|
|
63148
|
+
}
|
|
63149
|
+
const parsed = Number(rawPort);
|
|
63150
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
63151
|
+
console.error(`[ERR] Invalid port: ${rawPort}. Expected 1-65535.`);
|
|
63152
|
+
continue;
|
|
63153
|
+
}
|
|
63154
|
+
candidate = parsed;
|
|
63155
|
+
if (await isPortAvailable(candidate)) {
|
|
63156
|
+
return candidate;
|
|
63157
|
+
}
|
|
63158
|
+
continue;
|
|
63159
|
+
}
|
|
63160
|
+
if (choice === "3") {
|
|
63161
|
+
console.error("[ERR] Server startup cancelled.");
|
|
63162
|
+
process.exit(1);
|
|
63163
|
+
}
|
|
63164
|
+
console.error("[ERR] Invalid selection. Enter 1, 2, or 3.");
|
|
63165
|
+
}
|
|
62875
63166
|
}
|
|
62876
63167
|
async function ensureServerBinary(forceUpdate) {
|
|
62877
63168
|
const binDir = join4(homedir2(), ".isol8", "bin");
|
|
@@ -63083,6 +63374,7 @@ program2.command("cleanup").description("Remove orphaned isol8 containers (and o
|
|
|
63083
63374
|
async function resolveRunInput(file, opts) {
|
|
63084
63375
|
const config = loadConfig();
|
|
63085
63376
|
logger.debug("[Run] Config loaded");
|
|
63377
|
+
const hasExplicitNetFlag = process.argv.some((arg) => arg === "--net");
|
|
63086
63378
|
let code;
|
|
63087
63379
|
let codeUrl;
|
|
63088
63380
|
let codeHash;
|
|
@@ -63156,6 +63448,20 @@ async function resolveRunInput(file, opts) {
|
|
|
63156
63448
|
dependencies: config.dependencies,
|
|
63157
63449
|
remoteCode: config.remoteCode
|
|
63158
63450
|
};
|
|
63451
|
+
if (opts.install.length > 0 && !hasExplicitNetFlag) {
|
|
63452
|
+
engineOptions.network = "filtered";
|
|
63453
|
+
logger.debug("[Run] --install detected without explicit --net; using filtered network mode automatically");
|
|
63454
|
+
}
|
|
63455
|
+
if (opts.install.length > 0 && engineOptions.network === "filtered") {
|
|
63456
|
+
const runtimeRegistryAllowlist = getDefaultRegistryAllowPatterns(runtime);
|
|
63457
|
+
if (runtimeRegistryAllowlist.length > 0) {
|
|
63458
|
+
engineOptions.networkFilter = {
|
|
63459
|
+
whitelist: Array.from(new Set([...engineOptions.networkFilter?.whitelist ?? [], ...runtimeRegistryAllowlist])),
|
|
63460
|
+
blacklist: engineOptions.networkFilter?.blacklist ?? []
|
|
63461
|
+
};
|
|
63462
|
+
logger.debug(`[Run] Added default package registries for ${runtime}: ${runtimeRegistryAllowlist.join(", ")}`);
|
|
63463
|
+
}
|
|
63464
|
+
}
|
|
63159
63465
|
logger.debug(`[Run] Engine options: mode=${engineOptions.mode}, network=${engineOptions.network}`);
|
|
63160
63466
|
let fileExtension;
|
|
63161
63467
|
if (file) {
|
|
@@ -63231,6 +63537,19 @@ function detectRuntimeFromPath(pathValue) {
|
|
|
63231
63537
|
return;
|
|
63232
63538
|
}
|
|
63233
63539
|
}
|
|
63540
|
+
function getDefaultRegistryAllowPatterns(runtime) {
|
|
63541
|
+
switch (runtime) {
|
|
63542
|
+
case "python":
|
|
63543
|
+
return ["^pypi\\.org$", "^files\\.pythonhosted\\.org$"];
|
|
63544
|
+
case "node":
|
|
63545
|
+
case "bun":
|
|
63546
|
+
return ["^registry\\.npmjs\\.org$"];
|
|
63547
|
+
case "bash":
|
|
63548
|
+
return ["^dl-cdn\\.alpinelinux\\.org$"];
|
|
63549
|
+
default:
|
|
63550
|
+
return [];
|
|
63551
|
+
}
|
|
63552
|
+
}
|
|
63234
63553
|
function collect(value, previous) {
|
|
63235
63554
|
return previous.concat([value]);
|
|
63236
63555
|
}
|
|
@@ -63240,4 +63559,4 @@ if (!process.argv.slice(2).length) {
|
|
|
63240
63559
|
}
|
|
63241
63560
|
program2.parse();
|
|
63242
63561
|
|
|
63243
|
-
//# debugId=
|
|
63562
|
+
//# debugId=DC4F72D2D8660BF264756E2164756E21
|
package/dist/index.js
CHANGED
|
@@ -546,6 +546,73 @@ class Semaphore {
|
|
|
546
546
|
}
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
// src/engine/default-seccomp-profile.ts
|
|
550
|
+
var EMBEDDED_DEFAULT_SECCOMP_PROFILE;
|
|
551
|
+
var init_default_seccomp_profile = __esm(() => {
|
|
552
|
+
EMBEDDED_DEFAULT_SECCOMP_PROFILE = JSON.stringify({
|
|
553
|
+
defaultAction: "SCMP_ACT_ALLOW",
|
|
554
|
+
architectures: ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86", "SCMP_ARCH_X32", "SCMP_ARCH_AARCH64"],
|
|
555
|
+
syscalls: [
|
|
556
|
+
{
|
|
557
|
+
names: [
|
|
558
|
+
"acct",
|
|
559
|
+
"add_key",
|
|
560
|
+
"bpf",
|
|
561
|
+
"clock_adjtime",
|
|
562
|
+
"clock_settime",
|
|
563
|
+
"create_module",
|
|
564
|
+
"delete_module",
|
|
565
|
+
"finit_module",
|
|
566
|
+
"get_mempolicy",
|
|
567
|
+
"init_module",
|
|
568
|
+
"ioperm",
|
|
569
|
+
"iopl",
|
|
570
|
+
"kcmp",
|
|
571
|
+
"kexec_file_load",
|
|
572
|
+
"kexec_load",
|
|
573
|
+
"keyctl",
|
|
574
|
+
"lookup_dcookie",
|
|
575
|
+
"mbind",
|
|
576
|
+
"mount",
|
|
577
|
+
"move_pages",
|
|
578
|
+
"name_to_handle_at",
|
|
579
|
+
"open_by_handle_at",
|
|
580
|
+
"perf_event_open",
|
|
581
|
+
"pivot_root",
|
|
582
|
+
"process_vm_readv",
|
|
583
|
+
"process_vm_writev",
|
|
584
|
+
"ptrace",
|
|
585
|
+
"query_module",
|
|
586
|
+
"quotactl",
|
|
587
|
+
"reboot",
|
|
588
|
+
"request_key",
|
|
589
|
+
"set_mempolicy",
|
|
590
|
+
"setns",
|
|
591
|
+
"settimeofday",
|
|
592
|
+
"stime",
|
|
593
|
+
"swapon",
|
|
594
|
+
"swapoff",
|
|
595
|
+
"sysfs",
|
|
596
|
+
"syslog",
|
|
597
|
+
"umount",
|
|
598
|
+
"umount2",
|
|
599
|
+
"unshare",
|
|
600
|
+
"uselib",
|
|
601
|
+
"userfaultfd",
|
|
602
|
+
"ustat",
|
|
603
|
+
"vm86",
|
|
604
|
+
"vm86old"
|
|
605
|
+
],
|
|
606
|
+
action: "SCMP_ACT_ERRNO",
|
|
607
|
+
args: [],
|
|
608
|
+
comment: "",
|
|
609
|
+
includes: {},
|
|
610
|
+
excludes: {}
|
|
611
|
+
}
|
|
612
|
+
]
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
|
|
549
616
|
// src/engine/image-builder.ts
|
|
550
617
|
import { createHash as createHash2 } from "node:crypto";
|
|
551
618
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -1087,7 +1154,19 @@ function wrapWithTimeout(cmd, timeoutSec) {
|
|
|
1087
1154
|
function getInstallCommand(runtime, packages) {
|
|
1088
1155
|
switch (runtime) {
|
|
1089
1156
|
case "python":
|
|
1090
|
-
return [
|
|
1157
|
+
return [
|
|
1158
|
+
"pip",
|
|
1159
|
+
"install",
|
|
1160
|
+
"--user",
|
|
1161
|
+
"--no-cache-dir",
|
|
1162
|
+
"--break-system-packages",
|
|
1163
|
+
"--disable-pip-version-check",
|
|
1164
|
+
"--retries",
|
|
1165
|
+
"0",
|
|
1166
|
+
"--timeout",
|
|
1167
|
+
"15",
|
|
1168
|
+
...packages
|
|
1169
|
+
];
|
|
1091
1170
|
case "node":
|
|
1092
1171
|
return ["npm", "install", "--prefix", "/sandbox", ...packages];
|
|
1093
1172
|
case "bun":
|
|
@@ -1100,8 +1179,9 @@ function getInstallCommand(runtime, packages) {
|
|
|
1100
1179
|
throw new Error(`Unknown runtime for package install: ${runtime}`);
|
|
1101
1180
|
}
|
|
1102
1181
|
}
|
|
1103
|
-
async function installPackages(container, runtime, packages) {
|
|
1104
|
-
const
|
|
1182
|
+
async function installPackages(container, runtime, packages, timeoutMs) {
|
|
1183
|
+
const timeoutSec = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
1184
|
+
const cmd = wrapWithTimeout(getInstallCommand(runtime, packages), timeoutSec);
|
|
1105
1185
|
logger.debug(`Installing packages: ${JSON.stringify(cmd)}`);
|
|
1106
1186
|
const env = [
|
|
1107
1187
|
"PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
|
|
@@ -1112,6 +1192,12 @@ async function installPackages(container, runtime, packages) {
|
|
|
1112
1192
|
env.push("NPM_CONFIG_PREFIX=/sandbox/.npm-global");
|
|
1113
1193
|
env.push("NPM_CONFIG_CACHE=/sandbox/.npm-cache");
|
|
1114
1194
|
env.push("npm_config_cache=/sandbox/.npm-cache");
|
|
1195
|
+
env.push("NPM_CONFIG_FETCH_RETRIES=0");
|
|
1196
|
+
env.push("npm_config_fetch_retries=0");
|
|
1197
|
+
env.push("NPM_CONFIG_FETCH_RETRY_MINTIMEOUT=1000");
|
|
1198
|
+
env.push("npm_config_fetch_retry_mintimeout=1000");
|
|
1199
|
+
env.push("NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT=2000");
|
|
1200
|
+
env.push("npm_config_fetch_retry_maxtimeout=2000");
|
|
1115
1201
|
} else if (runtime === "bun") {
|
|
1116
1202
|
env.push("BUN_INSTALL_GLOBAL_DIR=/sandbox/.bun-global");
|
|
1117
1203
|
env.push("BUN_INSTALL_CACHE_DIR=/sandbox/.bun-cache");
|
|
@@ -1133,7 +1219,13 @@ async function installPackages(container, runtime, packages) {
|
|
|
1133
1219
|
const stderrStream = new PassThrough;
|
|
1134
1220
|
container.modem.demuxStream(stream, stdoutStream, stderrStream);
|
|
1135
1221
|
stderrStream.on("data", (chunk) => {
|
|
1136
|
-
|
|
1222
|
+
const text = chunk.toString();
|
|
1223
|
+
stderr += text;
|
|
1224
|
+
logger.debug(`[install:${runtime}:stderr] ${text.trimEnd()}`);
|
|
1225
|
+
});
|
|
1226
|
+
stdoutStream.on("data", (chunk) => {
|
|
1227
|
+
const text = chunk.toString();
|
|
1228
|
+
logger.debug(`[install:${runtime}:stdout] ${text.trimEnd()}`);
|
|
1137
1229
|
});
|
|
1138
1230
|
stream.on("end", async () => {
|
|
1139
1231
|
try {
|
|
@@ -1463,7 +1555,7 @@ class DockerIsol8 {
|
|
|
1463
1555
|
const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
|
|
1464
1556
|
await writeFileViaExec(container, filePath, request.code);
|
|
1465
1557
|
if (request.installPackages?.length) {
|
|
1466
|
-
await installPackages(container, request.runtime, request.installPackages);
|
|
1558
|
+
await installPackages(container, request.runtime, request.installPackages, timeoutMs);
|
|
1467
1559
|
}
|
|
1468
1560
|
if (request.files) {
|
|
1469
1561
|
for (const [fPath, fContent] of Object.entries(request.files)) {
|
|
@@ -1592,7 +1684,7 @@ class DockerIsol8 {
|
|
|
1592
1684
|
rawCmd = adapter.getCommand(req.code, filePath);
|
|
1593
1685
|
}
|
|
1594
1686
|
if (req.installPackages?.length) {
|
|
1595
|
-
await installPackages(container, req.runtime, req.installPackages);
|
|
1687
|
+
await installPackages(container, req.runtime, req.installPackages, timeoutMs);
|
|
1596
1688
|
}
|
|
1597
1689
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
1598
1690
|
let cmd;
|
|
@@ -1700,7 +1792,7 @@ class DockerIsol8 {
|
|
|
1700
1792
|
const rawCmd = adapter.getCommand(req.code, filePath);
|
|
1701
1793
|
const timeoutSec = Math.ceil(timeoutMs / 1000);
|
|
1702
1794
|
if (req.installPackages?.length) {
|
|
1703
|
-
await installPackages(this.container, req.runtime, req.installPackages);
|
|
1795
|
+
await installPackages(this.container, req.runtime, req.installPackages, timeoutMs);
|
|
1704
1796
|
}
|
|
1705
1797
|
let cmd;
|
|
1706
1798
|
if (req.stdin) {
|
|
@@ -1843,17 +1935,15 @@ class DockerIsol8 {
|
|
|
1843
1935
|
const profile = readFileSync3(this.security.customProfilePath, "utf-8");
|
|
1844
1936
|
opts.push(`seccomp=${profile}`);
|
|
1845
1937
|
} catch (e) {
|
|
1846
|
-
|
|
1938
|
+
throw new Error(`Failed to load custom seccomp profile at ${this.security.customProfilePath}: ${e}`);
|
|
1847
1939
|
}
|
|
1848
1940
|
return opts;
|
|
1849
1941
|
}
|
|
1850
1942
|
try {
|
|
1851
1943
|
const profile = this.loadDefaultSeccompProfile();
|
|
1852
|
-
|
|
1853
|
-
opts.push(`seccomp=${profile}`);
|
|
1854
|
-
}
|
|
1944
|
+
opts.push(`seccomp=${profile}`);
|
|
1855
1945
|
} catch (e) {
|
|
1856
|
-
|
|
1946
|
+
throw new Error(`Failed to load default seccomp profile: ${e}`);
|
|
1857
1947
|
}
|
|
1858
1948
|
return opts;
|
|
1859
1949
|
}
|
|
@@ -1866,8 +1956,11 @@ class DockerIsol8 {
|
|
|
1866
1956
|
if (existsSync4(prodPath)) {
|
|
1867
1957
|
return readFileSync3(prodPath, "utf-8");
|
|
1868
1958
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1959
|
+
if (EMBEDDED_DEFAULT_SECCOMP_PROFILE.length > 0) {
|
|
1960
|
+
logger.debug(`Default seccomp profile file not found. Using embedded profile. Tried: ${devPath.pathname}, ${prodPath.pathname}`);
|
|
1961
|
+
return EMBEDDED_DEFAULT_SECCOMP_PROFILE;
|
|
1962
|
+
}
|
|
1963
|
+
throw new Error("Embedded default seccomp profile is unavailable");
|
|
1871
1964
|
}
|
|
1872
1965
|
buildEnv(extra) {
|
|
1873
1966
|
const env = [
|
|
@@ -2092,6 +2185,7 @@ var init_docker = __esm(() => {
|
|
|
2092
2185
|
init_logger();
|
|
2093
2186
|
init_audit();
|
|
2094
2187
|
init_code_fetcher();
|
|
2188
|
+
init_default_seccomp_profile();
|
|
2095
2189
|
init_image_builder();
|
|
2096
2190
|
init_pool();
|
|
2097
2191
|
MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
@@ -2349,7 +2443,7 @@ init_logger();
|
|
|
2349
2443
|
// package.json
|
|
2350
2444
|
var package_default = {
|
|
2351
2445
|
name: "isol8",
|
|
2352
|
-
version: "0.11.
|
|
2446
|
+
version: "0.11.2",
|
|
2353
2447
|
description: "Secure code execution engine for AI agents",
|
|
2354
2448
|
author: "Illusion47586",
|
|
2355
2449
|
license: "MIT",
|
|
@@ -2501,6 +2595,50 @@ async function createServer(options) {
|
|
|
2501
2595
|
logger.debug(`[Server] Auto-prune: ${config.cleanup.autoPrune}`);
|
|
2502
2596
|
const app = new Hono;
|
|
2503
2597
|
const globalSemaphore = new Semaphore(config.maxConcurrent);
|
|
2598
|
+
let pruneInterval;
|
|
2599
|
+
let cleanupInFlight = null;
|
|
2600
|
+
const cleanupSessions = async () => {
|
|
2601
|
+
let removed = 0;
|
|
2602
|
+
let failed = 0;
|
|
2603
|
+
const errors = [];
|
|
2604
|
+
for (const [id, session] of sessions) {
|
|
2605
|
+
try {
|
|
2606
|
+
await session.engine.stop();
|
|
2607
|
+
removed++;
|
|
2608
|
+
} catch (err) {
|
|
2609
|
+
failed++;
|
|
2610
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2611
|
+
errors.push(`${id}: ${errorMsg}`);
|
|
2612
|
+
} finally {
|
|
2613
|
+
sessions.delete(id);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
return { removed, failed, errors };
|
|
2617
|
+
};
|
|
2618
|
+
const runCleanup = async (includeImages) => {
|
|
2619
|
+
if (cleanupInFlight) {
|
|
2620
|
+
return cleanupInFlight;
|
|
2621
|
+
}
|
|
2622
|
+
cleanupInFlight = (async () => {
|
|
2623
|
+
logger.info(`[Server] Starting cleanup (sessions=true containers=true images=${includeImages})`);
|
|
2624
|
+
const sessionsResult = await cleanupSessions();
|
|
2625
|
+
const containersResult = await DockerIsol82.cleanup();
|
|
2626
|
+
const result = {
|
|
2627
|
+
sessions: sessionsResult,
|
|
2628
|
+
containers: containersResult
|
|
2629
|
+
};
|
|
2630
|
+
if (includeImages) {
|
|
2631
|
+
result.images = await DockerIsol82.cleanupImages();
|
|
2632
|
+
}
|
|
2633
|
+
logger.info(`[Server] Cleanup complete: sessions=${result.sessions.removed}/${result.sessions.failed} containers=${result.containers.removed}/${result.containers.failed}${result.images ? ` images=${result.images.removed}/${result.images.failed}` : ""}`);
|
|
2634
|
+
return result;
|
|
2635
|
+
})();
|
|
2636
|
+
try {
|
|
2637
|
+
return await cleanupInFlight;
|
|
2638
|
+
} finally {
|
|
2639
|
+
cleanupInFlight = null;
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2504
2642
|
app.use("*", authMiddleware(options.apiKey));
|
|
2505
2643
|
app.get("/health", (c) => c.json({ status: "ok", version: VERSION }));
|
|
2506
2644
|
app.post("/execute", async (c) => {
|
|
@@ -2681,8 +2819,21 @@ async function createServer(options) {
|
|
|
2681
2819
|
}
|
|
2682
2820
|
return c.json({ ok: true });
|
|
2683
2821
|
});
|
|
2822
|
+
app.post("/cleanup", async (c) => {
|
|
2823
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2824
|
+
const includeImages = body.images ?? true;
|
|
2825
|
+
logger.debug(`[Server] POST /cleanup images=${includeImages}`);
|
|
2826
|
+
try {
|
|
2827
|
+
const result = await runCleanup(includeImages);
|
|
2828
|
+
return c.json({ ok: true, ...result });
|
|
2829
|
+
} catch (err) {
|
|
2830
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2831
|
+
logger.error(`[Server] Cleanup failed: ${message}`);
|
|
2832
|
+
return c.json({ error: message }, 500);
|
|
2833
|
+
}
|
|
2834
|
+
});
|
|
2684
2835
|
if (config.cleanup.autoPrune) {
|
|
2685
|
-
setInterval(async () => {
|
|
2836
|
+
pruneInterval = setInterval(async () => {
|
|
2686
2837
|
const maxAge = config.cleanup.maxContainerAgeMs;
|
|
2687
2838
|
const now = Date.now();
|
|
2688
2839
|
for (const [id, session] of sessions) {
|
|
@@ -2700,7 +2851,15 @@ async function createServer(options) {
|
|
|
2700
2851
|
return {
|
|
2701
2852
|
app,
|
|
2702
2853
|
fetch: app.fetch,
|
|
2703
|
-
port: options.port
|
|
2854
|
+
port: options.port,
|
|
2855
|
+
cleanup: async (includeImages = true) => runCleanup(includeImages),
|
|
2856
|
+
shutdown: async (includeImages = true) => {
|
|
2857
|
+
if (pruneInterval) {
|
|
2858
|
+
clearInterval(pruneInterval);
|
|
2859
|
+
pruneInterval = undefined;
|
|
2860
|
+
}
|
|
2861
|
+
await runCleanup(includeImages);
|
|
2862
|
+
}
|
|
2704
2863
|
};
|
|
2705
2864
|
}
|
|
2706
2865
|
export {
|
|
@@ -2717,4 +2876,4 @@ export {
|
|
|
2717
2876
|
BunAdapter
|
|
2718
2877
|
};
|
|
2719
2878
|
|
|
2720
|
-
//# debugId=
|
|
2879
|
+
//# debugId=C10FBC887CAF691764756E2164756E21
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded default seccomp profile.
|
|
3
|
+
*
|
|
4
|
+
* This keeps strict seccomp available in standalone compiled binaries where
|
|
5
|
+
* docker/seccomp-profile.json may not be present on disk.
|
|
6
|
+
*/
|
|
7
|
+
export declare const EMBEDDED_DEFAULT_SECCOMP_PROFILE: string;
|
|
8
|
+
//# sourceMappingURL=default-seccomp-profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-seccomp-profile.d.ts","sourceRoot":"","sources":["../../../src/engine/default-seccomp-profile.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,QA6D3C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../../src/engine/docker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,MAAM,MAAM,WAAW,CAAC;AAG/B,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EAEf,WAAW,EAEX,YAAY,EAKZ,YAAY,EACZ,WAAW,EACZ,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"docker.d.ts","sourceRoot":"","sources":["../../../src/engine/docker.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,23 @@ export interface ServerOptions {
|
|
|
15
15
|
/** Enable debug logging for internal server operations. */
|
|
16
16
|
debug?: boolean;
|
|
17
17
|
}
|
|
18
|
+
interface CleanupResult {
|
|
19
|
+
sessions: {
|
|
20
|
+
removed: number;
|
|
21
|
+
failed: number;
|
|
22
|
+
errors: string[];
|
|
23
|
+
};
|
|
24
|
+
containers: {
|
|
25
|
+
removed: number;
|
|
26
|
+
failed: number;
|
|
27
|
+
errors: string[];
|
|
28
|
+
};
|
|
29
|
+
images?: {
|
|
30
|
+
removed: number;
|
|
31
|
+
failed: number;
|
|
32
|
+
errors: string[];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
18
35
|
/**
|
|
19
36
|
* Creates and configures the isol8 HTTP server.
|
|
20
37
|
*
|
|
@@ -36,5 +53,8 @@ export declare function createServer(options: ServerOptions): Promise<{
|
|
|
36
53
|
app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
37
54
|
fetch: (request: Request, Env?: unknown, executionCtx?: import("hono").ExecutionContext) => Response | Promise<Response>;
|
|
38
55
|
port: number;
|
|
56
|
+
cleanup: (includeImages?: boolean) => Promise<CleanupResult>;
|
|
57
|
+
shutdown: (includeImages?: boolean) => Promise<void>;
|
|
39
58
|
}>;
|
|
59
|
+
export {};
|
|
40
60
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAaD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,aAAa
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAaD,UAAU,aAAa;IACrB,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAClE,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAChE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,aAAa;;;;;;GAyWxD"}
|