isol8 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,8 +11,9 @@ Secure code execution engine for AI agents. Run untrusted Python, Node.js, Bun,
11
11
  - **Security first** — read-only rootfs, `no-new-privileges`, PID/memory/CPU limits
12
12
  - **Network control** — `none` (default), `host`, or `filtered` (HTTP/HTTPS proxy with regex whitelist/blacklist)
13
13
  - **File I/O** — upload files into and download files from sandboxes
14
- - **Runtime packages** — install pip/npm/bun packages on-the-fly via `installPackages`
15
- - **Secret masking** — environment variables are scrubbed from output
14
+ - **Runtime packages** — install pip/npm/bun packages on-the-fly (`--install`)
15
+ - **Modern Node.js** — defaults to ESM (`.mjs`), supports CommonJS (`.cjs`)
16
+ - **Secret masking** — environment variables are scrubbed from output
16
17
  - **Output truncation** — prevents runaway stdout (default 1MB cap)
17
18
  - **Remote mode** — run an HTTP server and execute from anywhere
18
19
  - **Embeddable** — use as a TypeScript library in your own project
@@ -103,6 +104,8 @@ isol8 run script.py --host http://server:3000 --key my-api-key
103
104
  | `--out <file>` | Write stdout to file | — |
104
105
  | `--no-stream` | Disable real-time output streaming | `false` |
105
106
  | `--persistent` | Keep container alive between runs | `false` |
107
+ | `--persist` | Keep container after execution for inspection/debugging | `false` |
108
+ | `--debug` | Enable debug logging for internal engine operations | `false` |
106
109
  | `--timeout <ms>` | Execution timeout in milliseconds | `30000` |
107
110
  | `--memory <limit>` | Memory limit (e.g. `512m`, `1g`) | `512m` |
108
111
  | `--cpu <limit>` | CPU limit as fraction (e.g. `0.5`, `2.0`) | `1.0` |
package/dist/cli.js CHANGED
@@ -6929,6 +6929,11 @@ var require_utils2 = __commonJS((exports, module) => {
6929
6929
  };
6930
6930
  });
6931
6931
 
6932
+ // node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node
6933
+ var require_sshcrypto = __commonJS((exports, module) => {
6934
+ module.exports = __require("./sshcrypto-0209sx47.node");
6935
+ });
6936
+
6932
6937
  // node_modules/ssh2/lib/protocol/crypto/poly1305.js
6933
6938
  var require_poly1305 = __commonJS((exports, module) => {
6934
6939
  var __dirname = "/home/runner/work/isol8/isol8/node_modules/ssh2/lib/protocol/crypto", __filename = "/home/runner/work/isol8/isol8/node_modules/ssh2/lib/protocol/crypto/poly1305.js";
@@ -7415,7 +7420,7 @@ var require_crypto = __commonJS((exports, module) => {
7415
7420
  var ChaChaPolyDecipher;
7416
7421
  var GenericDecipher;
7417
7422
  try {
7418
- binding = (()=>{throw new Error("Cannot require module "+"./crypto/build/Release/sshcrypto.node");})();
7423
+ binding = require_sshcrypto();
7419
7424
  ({
7420
7425
  AESGCMCipher,
7421
7426
  ChaChaPolyCipher,
@@ -54793,7 +54798,8 @@ function mergeConfig(defaults, overrides) {
54793
54798
  dependencies: {
54794
54799
  ...defaults.dependencies,
54795
54800
  ...overrides.dependencies
54796
- }
54801
+ },
54802
+ debug: overrides.debug ?? defaults.debug
54797
54803
  };
54798
54804
  }
54799
54805
  var DEFAULT_CONFIG;
@@ -54816,7 +54822,8 @@ var init_config = __esm(() => {
54816
54822
  autoPrune: true,
54817
54823
  maxContainerAgeMs: 3600000
54818
54824
  },
54819
- dependencies: {}
54825
+ dependencies: {},
54826
+ debug: false
54820
54827
  };
54821
54828
  });
54822
54829
 
@@ -54826,9 +54833,12 @@ var init_adapter = __esm(() => {
54826
54833
  adapters = new Map;
54827
54834
  extensionMap = new Map;
54828
54835
  RuntimeRegistry = {
54829
- register(adapter) {
54836
+ register(adapter, aliases = []) {
54830
54837
  adapters.set(adapter.name, adapter);
54831
54838
  extensionMap.set(adapter.getFileExtension(), adapter);
54839
+ for (const ext of aliases) {
54840
+ extensionMap.set(ext, adapter);
54841
+ }
54832
54842
  },
54833
54843
  get(name) {
54834
54844
  const adapter = adapters.get(name);
@@ -54926,7 +54936,7 @@ var init_node = __esm(() => {
54926
54936
  return ["node", "-e", code];
54927
54937
  },
54928
54938
  getFileExtension() {
54929
- return ".js";
54939
+ return ".mjs";
54930
54940
  }
54931
54941
  };
54932
54942
  });
@@ -54964,12 +54974,43 @@ var init_runtime = __esm(() => {
54964
54974
  init_node();
54965
54975
  init_python();
54966
54976
  RuntimeRegistry.register(PythonAdapter);
54967
- RuntimeRegistry.register(NodeAdapter);
54977
+ RuntimeRegistry.register(NodeAdapter, [".js", ".cjs"]);
54968
54978
  RuntimeRegistry.register(BunAdapter);
54969
54979
  RuntimeRegistry.register(bashAdapter);
54970
54980
  RuntimeRegistry.register(DenoAdapter);
54971
54981
  });
54972
54982
 
54983
+ // src/utils/logger.ts
54984
+ var exports_logger = {};
54985
+ __export(exports_logger, {
54986
+ logger: () => logger
54987
+ });
54988
+
54989
+ class Logger {
54990
+ debugMode = false;
54991
+ setDebug(enabled) {
54992
+ this.debugMode = enabled;
54993
+ }
54994
+ debug(...args) {
54995
+ if (this.debugMode) {
54996
+ console.log("[DEBUG]", ...args);
54997
+ }
54998
+ }
54999
+ info(...args) {
55000
+ console.log(...args);
55001
+ }
55002
+ warn(...args) {
55003
+ console.warn("[WARN]", ...args);
55004
+ }
55005
+ error(...args) {
55006
+ console.error("[ERROR]", ...args);
55007
+ }
55008
+ }
55009
+ var logger;
55010
+ var init_logger = __esm(() => {
55011
+ logger = new Logger;
55012
+ });
55013
+
54973
55014
  // src/engine/concurrency.ts
54974
55015
  class Semaphore {
54975
55016
  max;
@@ -55078,26 +55119,32 @@ class ContainerPool {
55078
55119
  ...this.createOptions,
55079
55120
  Image: image
55080
55121
  });
55122
+ logger.debug(`[Pool] Container ${container.id} created for image: ${image}`);
55081
55123
  await container.start();
55124
+ logger.debug(`[Pool] Container ${container.id} started`);
55082
55125
  return container;
55083
55126
  }
55084
55127
  replenish(image) {
55085
55128
  if (this.replenishing.has(image)) {
55129
+ logger.debug(`[Pool] Replenishment for ${image} already in progress`);
55086
55130
  return;
55087
55131
  }
55088
55132
  this.replenishing.add(image);
55133
+ logger.debug(`[Pool] Starting background replenishment for image: ${image}`);
55089
55134
  const promise = this.createContainer(image).then((container) => {
55090
55135
  const pool = this.pools.get(image) ?? [];
55091
55136
  if (pool.length < this.poolSize) {
55092
55137
  pool.push({ container, createdAt: Date.now() });
55093
55138
  this.pools.set(image, pool);
55139
+ logger.debug(`[Pool] Replenished container ${container.id} added to pool for ${image}. Pool size: ${pool.length}`);
55094
55140
  } else {
55141
+ logger.debug(`[Pool] Replenished container ${container.id} not needed (pool for ${image} is full), destroying`);
55095
55142
  container.remove({ force: true }).catch((err) => {
55096
- console.error(`[Pool] Error destroying unneeded replenished container ${container.id}:`, err);
55143
+ logger.error(`[Pool] Error destroying unneeded replenished container ${container.id}:`, err);
55097
55144
  });
55098
55145
  }
55099
55146
  }).catch((err) => {
55100
- console.error(`[Pool] Error during replenishment for ${image}:`, err);
55147
+ logger.error(`[Pool] Error during replenishment for ${image}:`, err);
55101
55148
  }).finally(() => {
55102
55149
  this.replenishing.delete(image);
55103
55150
  this.pendingReplenishments.delete(promise);
@@ -55105,6 +55152,9 @@ class ContainerPool {
55105
55152
  this.pendingReplenishments.add(promise);
55106
55153
  }
55107
55154
  }
55155
+ var init_pool = __esm(() => {
55156
+ init_logger();
55157
+ });
55108
55158
 
55109
55159
  // src/engine/utils.ts
55110
55160
  var exports_utils = {};
@@ -55301,7 +55351,7 @@ function getInstallCommand(runtime, packages) {
55301
55351
  }
55302
55352
  async function installPackages(container, runtime, packages) {
55303
55353
  const cmd = getInstallCommand(runtime, packages);
55304
- console.error(`[DEBUG] Installing packages: ${JSON.stringify(cmd)}`);
55354
+ logger.debug(`Installing packages: ${JSON.stringify(cmd)}`);
55305
55355
  const env2 = [
55306
55356
  "PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
55307
55357
  ];
@@ -55365,6 +55415,7 @@ class DockerIsol8 {
55365
55415
  semaphore;
55366
55416
  sandboxSize;
55367
55417
  tmpSize;
55418
+ persist;
55368
55419
  container = null;
55369
55420
  persistentRuntime = null;
55370
55421
  pool = null;
@@ -55384,6 +55435,10 @@ class DockerIsol8 {
55384
55435
  this.semaphore = new Semaphore(maxConcurrent);
55385
55436
  this.sandboxSize = options.sandboxSize ?? "512m";
55386
55437
  this.tmpSize = options.tmpSize ?? "256m";
55438
+ this.persist = options.persist ?? false;
55439
+ if (options.debug) {
55440
+ logger.setDebug(true);
55441
+ }
55387
55442
  }
55388
55443
  async start() {}
55389
55444
  async stop() {
@@ -55459,7 +55514,8 @@ class DockerIsol8 {
55459
55514
  if (this.network === "filtered") {
55460
55515
  await startProxy(container, this.networkFilter);
55461
55516
  }
55462
- const filePath = `${SANDBOX_WORKDIR}/main${adapter.getFileExtension()}`;
55517
+ const ext = req.fileExtension ?? adapter.getFileExtension();
55518
+ const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
55463
55519
  await writeFileViaExec(container, filePath, req.code);
55464
55520
  if (req.installPackages?.length) {
55465
55521
  await installPackages(container, req.runtime, req.installPackages);
@@ -55490,9 +55546,13 @@ class DockerIsol8 {
55490
55546
  const execStream = await exec.start({ Tty: false });
55491
55547
  yield* this.streamExecOutput(execStream, exec, container, timeoutMs);
55492
55548
  } finally {
55493
- try {
55494
- await container.remove({ force: true });
55495
- } catch {}
55549
+ if (this.persist) {
55550
+ logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
55551
+ } else {
55552
+ try {
55553
+ await container.remove({ force: true });
55554
+ } catch {}
55555
+ }
55496
55556
  }
55497
55557
  } finally {
55498
55558
  this.semaphore.release();
@@ -55533,7 +55593,8 @@ class DockerIsol8 {
55533
55593
  if (this.network === "filtered") {
55534
55594
  await startProxy(container, this.networkFilter);
55535
55595
  }
55536
- const filePath = `${SANDBOX_WORKDIR}/main${adapter.getFileExtension()}`;
55596
+ const ext = req.fileExtension ?? adapter.getFileExtension();
55597
+ const filePath = `${SANDBOX_WORKDIR}/main${ext}`;
55537
55598
  await writeFileViaExec(container, filePath, req.code);
55538
55599
  if (req.installPackages?.length) {
55539
55600
  await installPackages(container, req.runtime, req.installPackages);
@@ -55579,7 +55640,11 @@ class DockerIsol8 {
55579
55640
  ...req.outputPaths ? { files: await this.retrieveFiles(container, req.outputPaths) } : {}
55580
55641
  };
55581
55642
  } finally {
55582
- await this.pool.release(container, image);
55643
+ if (this.persist) {
55644
+ logger.debug(`[Persist] Leaving container running for inspection: ${container.id}`);
55645
+ } else {
55646
+ await this.pool.release(container, image);
55647
+ }
55583
55648
  }
55584
55649
  }
55585
55650
  async executePersistent(req) {
@@ -55590,7 +55655,8 @@ class DockerIsol8 {
55590
55655
  } else if (this.persistentRuntime?.name !== adapter.name) {
55591
55656
  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.`);
55592
55657
  }
55593
- const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${adapter.getFileExtension()}`;
55658
+ const ext = req.fileExtension ?? adapter.getFileExtension();
55659
+ const filePath = `${SANDBOX_WORKDIR}/exec_${Date.now()}${ext}`;
55594
55660
  if (this.readonlyRootFs) {
55595
55661
  await writeFileViaExec(this.container, filePath, req.code);
55596
55662
  } else {
@@ -55878,6 +55944,8 @@ class DockerIsol8 {
55878
55944
  var import_dockerode, SANDBOX_WORKDIR = "/sandbox", MAX_OUTPUT_BYTES, PROXY_PORT = 8118, PROXY_STARTUP_TIMEOUT_MS = 5000, PROXY_POLL_INTERVAL_MS = 100;
55879
55945
  var init_docker = __esm(() => {
55880
55946
  init_runtime();
55947
+ init_logger();
55948
+ init_pool();
55881
55949
  import_dockerode = __toESM(require_docker(), 1);
55882
55950
  MAX_OUTPUT_BYTES = 1024 * 1024;
55883
55951
  });
@@ -61220,8 +61288,8 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
61220
61288
  console.log(`
61221
61289
  [DONE] Setup complete!`);
61222
61290
  });
61223
- program2.command("run").description("Execute code in isol8").argument("[file]", "Script file to execute").option("-e, --eval <code>", "Execute inline code string").option("-r, --runtime <name>", "Force runtime (python, node, bun, deno, bash)").option("--net <mode>", "Network mode: none, host, filtered", "none").option("--allow <regex>", "Whitelist regex for filtered mode (repeatable)", collect, []).option("--deny <regex>", "Blacklist regex for filtered mode (repeatable)", collect, []).option("--out <file>", "Write output to file").option("--persistent", "Use persistent container").option("--timeout <ms>", "Execution timeout in milliseconds").option("--memory <limit>", "Memory limit (e.g. 512m, 1g)").option("--cpu <limit>", "CPU limit as fraction (e.g. 0.5, 2.0)").option("--image <name>", "Override Docker image").option("--pids-limit <n>", "Maximum number of processes").option("--writable", "Disable read-only root filesystem").option("--max-output <bytes>", "Maximum output size in bytes").option("--secret <KEY=VALUE>", "Secret env var (repeatable, values masked)", collect, []).option("--sandbox-size <size>", "Sandbox tmpfs size (e.g. 128m)").option("--tmp-size <size>", "Tmp tmpfs size (e.g. 256m, 512m)").option("--stdin <data>", "Data to pipe to stdin").option("--install <package>", "Install package for runtime (repeatable)", collect, []).option("--host <url>", "Execute on remote server").option("--key <key>", "API key for remote server").option("--no-stream", "Disable real-time output streaming").action(async (file, opts) => {
61224
- const { code, runtime, engineOptions, engine, stdinData } = await resolveRunInput(file, opts);
61291
+ program2.command("run").description("Execute code in isol8").argument("[file]", "Script file to execute").option("-e, --eval <code>", "Execute inline code string").option("-r, --runtime <name>", "Force runtime (python, node, bun, deno, bash)").option("--net <mode>", "Network mode: none, host, filtered", "none").option("--allow <regex>", "Whitelist regex for filtered mode (repeatable)", collect, []).option("--deny <regex>", "Blacklist regex for filtered mode (repeatable)", collect, []).option("--out <file>", "Write output to file").option("--persistent", "Use persistent container").option("--timeout <ms>", "Execution timeout in milliseconds").option("--memory <limit>", "Memory limit (e.g. 512m, 1g)").option("--cpu <limit>", "CPU limit as fraction (e.g. 0.5, 2.0)").option("--image <name>", "Override Docker image").option("--pids-limit <n>", "Maximum number of processes").option("--writable", "Disable read-only root filesystem").option("--max-output <bytes>", "Maximum output size in bytes").option("--secret <KEY=VALUE>", "Secret env var (repeatable, values masked)", collect, []).option("--sandbox-size <size>", "Sandbox tmpfs size (e.g. 128m)").option("--tmp-size <size>", "Tmp tmpfs size (e.g. 256m, 512m)").option("--stdin <data>", "Data to pipe to stdin").option("--install <package>", "Install package for runtime (repeatable)", collect, []).option("--host <url>", "Execute on remote server").option("--key <key>", "API key for remote server").option("--no-stream", "Disable real-time output streaming").option("--debug", "Enable debug logging").option("--persist", "Keep container running after execution for inspection").action(async (file, opts) => {
61292
+ const { code, runtime, engineOptions, engine, stdinData, fileExtension } = await resolveRunInput(file, opts);
61225
61293
  const cleanup = async () => {
61226
61294
  await engine.stop();
61227
61295
  process.exit(0);
@@ -61238,7 +61306,8 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
61238
61306
  runtime,
61239
61307
  timeoutMs: engineOptions.timeoutMs,
61240
61308
  ...stdinData ? { stdin: stdinData } : {},
61241
- ...opts.install.length > 0 ? { installPackages: opts.install } : {}
61309
+ ...opts.install.length > 0 ? { installPackages: opts.install } : {},
61310
+ fileExtension
61242
61311
  };
61243
61312
  if (opts.stream !== false) {
61244
61313
  spinner.stop();
@@ -61480,9 +61549,21 @@ async function resolveRunInput(file, opts) {
61480
61549
  ...opts.pidsLimit ? { pidsLimit: Number.parseInt(opts.pidsLimit, 10) } : {},
61481
61550
  ...opts.writable ? { readonlyRootFs: false } : {},
61482
61551
  ...opts.maxOutput ? { maxOutputSize: Number.parseInt(opts.maxOutput, 10) } : {},
61483
- ...opts.sandboxSize ? { sandboxSize: opts.sandboxSize } : {},
61484
- ...opts.tmpSize ? { tmpSize: opts.tmpSize } : {}
61552
+ ...opts.tmpSize ? { tmpSize: opts.tmpSize } : {},
61553
+ debug: opts.debug ?? config.debug,
61554
+ persist: opts.persist ?? false
61485
61555
  };
61556
+ if (engineOptions.debug) {
61557
+ const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), exports_logger));
61558
+ logger2.setDebug(true);
61559
+ }
61560
+ let fileExtension;
61561
+ if (file) {
61562
+ const ext = file.substring(file.lastIndexOf("."));
61563
+ if (ext) {
61564
+ fileExtension = ext;
61565
+ }
61566
+ }
61486
61567
  const secrets = {};
61487
61568
  for (const s of opts.secret ?? []) {
61488
61569
  const idx = s.indexOf("=");
@@ -61505,7 +61586,7 @@ async function resolveRunInput(file, opts) {
61505
61586
  } else {
61506
61587
  engine = new DockerIsol8(engineOptions, config.maxConcurrent);
61507
61588
  }
61508
- return { code, runtime, engineOptions, engine, stdinData };
61589
+ return { code, runtime, engineOptions, engine, stdinData, fileExtension };
61509
61590
  }
61510
61591
  function collect(value, previous) {
61511
61592
  return previous.concat([value]);
@@ -61516,4 +61597,4 @@ if (!process.argv.slice(2).length) {
61516
61597
  }
61517
61598
  program2.parse();
61518
61599
 
61519
- //# debugId=56782BF23191267E64756E2164756E21
61600
+ //# debugId=9AD6B80BEBAE034064756E2164756E21