kern-sandbox 0.2.38 → 0.2.40

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.
Files changed (3) hide show
  1. package/README.md +26 -12
  2. package/index.js +23 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,17 +1,31 @@
1
- # kern-sandbox (Node.js / TypeScript)
1
+ <div align="center">
2
2
 
3
- **Run LLM-generated code in a fast, real sandbox, one fresh box per call.**
3
+ <img src="https://raw.githubusercontent.com/getkern/kern/main/assets/brand/kern-logo.png" width="220" alt="kern">
4
4
 
5
- Fast means milliseconds, and it is two numbers rather than one: the box is the cheap part, and an
6
- interpreter starting inside it costs more than the box does. Both depend on your machine, so they are
7
- measured under [Prewarming](https://github.com/getkern/kern/blob/main/bindings/node/README.md#prewarming-a-box-ready-before-the-call-arrives) with the machine and the method beside them, and the
8
- runtime's own are in [BENCHMARKS.md](https://github.com/getkern/kern/blob/main/BENCHMARKS.md).
5
+ # Kern Sandbox
9
6
 
10
- `kern-sandbox` is the Node and TypeScript binding for **[kern](https://getkern.dev)**: a rootless,
11
- kernel-enforced sandbox out of one static binary, with no daemon, no VM and no cloud. An agent's
12
- tool-call, a model's generated snippet, a CI step: code that runs before anyone reads it gets its own
13
- box, and the box is thrown away after. A hundred calls are a hundred boxes and 1.4 s in total, with
14
- nothing left behind, and when state has to carry across them there is
7
+ **Your model writes the code. This runs it where it can't touch your machine.**
8
+
9
+ <sub>**Works with** Claude Code · Cursor · Claude Desktop · LM Studio · LangChain · pi</sub>
10
+
11
+ [![npm](https://img.shields.io/npm/v/kern-sandbox?label=npm&color=0b7285)](https://www.npmjs.com/package/kern-sandbox)
12
+ [![PyPI](https://img.shields.io/pypi/v/kern-sandbox?label=PyPI&color=0b7285)](https://pypi.org/project/kern-sandbox/)
13
+ [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/getkern/kern/blob/main/LICENSE)
14
+
15
+ <sub>rootless · no daemon · no socket · no VM · no cloud · no account</sub>
16
+
17
+ **[The runtime](https://github.com/getkern/kern)** ·
18
+ **[MCP server](https://github.com/getkern/kern/blob/main/docs/MCP.md)** ·
19
+ **[Security model](https://github.com/getkern/kern/blob/main/SECURITY.md)** ·
20
+ **[Benchmarks](https://github.com/getkern/kern/blob/main/BENCHMARKS.md)**
21
+
22
+ </div>
23
+
24
+ An agent's tool-call, a generated snippet, a notebook cell, a CI step: it arrives, you run it, and
25
+ nobody has read it first.
26
+
27
+ `kern-sandbox` is the Node and TypeScript binding for **[kern](https://getkern.dev)**. Every call
28
+ gets its own box, thrown away after; when state has to carry across calls there is
15
29
  [a session](#a-session-files-persist-processes-are-ephemeral) and a warm interpreter.
16
30
 
17
31
  Network off, memory and PID caps the kernel enforces **where your host delegates them**,
@@ -288,7 +302,7 @@ RUN python3 -m compileall -q -j 0 /usr/local/lib/python3.12
288
302
 
289
303
  Build it once and pass it: `run_code(..., image="my-python")`. The default stays the stock tag,
290
304
  because an SDK that silently required a custom image would be worse than one that costs 29 ms and
291
- says so. Measured on an Intel i7-14700KF, Linux 7.0.0, idle; [BENCHMARKS.md](https://github.com/getkern/kern/blob/main/BENCHMARKS.md) has the method.
305
+ says so. Measured rootless on an idle machine; [BENCHMARKS.md](https://github.com/getkern/kern/blob/main/BENCHMARKS.md) has the method.
292
306
 
293
307
  ## Prewarming: a box ready before the call arrives
294
308
 
package/index.js CHANGED
@@ -36,7 +36,7 @@ const crypto = require("crypto");
36
36
  const zlib = require("zlib");
37
37
  const { spawn, spawnSync } = require("child_process");
38
38
 
39
- const VERSION = "0.2.38";
39
+ const VERSION = "0.2.40";
40
40
 
41
41
  const DEFAULT_IMAGE = "python:3.12-slim";
42
42
  const WORKSPACE = "/workspace"; // where the persistent workspace is mounted inside every box
@@ -285,9 +285,22 @@ function pycSourceId(image) {
285
285
  const root = path.join(cacheHome(), "kern", "images");
286
286
  const safe = sanitizeRef(image);
287
287
  const h = crypto.createHash("sha256");
288
- for (const suffix of [".image", ".ok"]) {
289
- h.update(fs.readFileSync(path.join(root, safe + suffix)).subarray(0, 4096));
290
- h.update(Buffer.from([0]));
288
+ // THE CONFIG, which changes when ENTRYPOINT/ENV/WORKDIR/USER do.
289
+ h.update(fs.readFileSync(path.join(root, `${safe}.image`)).subarray(0, 4096));
290
+ // THE SENTINEL'S STAMP, NOT ITS BYTES. `.ok` holds the REFERENCE, so its contents are the tag and
291
+ // never move when the tag does: hashing them missed the commonest case there is, a rebuilt rootfs
292
+ // under an unchanged config. Its mtime and length are what kern ITSELF uses to decide an image's
293
+ // content changed, because a re-pull rewrites the sentinel last. Measured: a real re-pull leaves
294
+ // the config byte-identical and moves this.
295
+ const st = fs.statSync(path.join(root, `${safe}.ok`), { bigint: true });
296
+ h.update(`${st.mtimeNs}:${st.size}`);
297
+ // AND THE LAYER MANIFEST FOR A BUILT IMAGE, which names its layers by content. A pulled image has
298
+ // none, and that absence is part of the identity: an image that stops being layered is not the
299
+ // same image.
300
+ try {
301
+ h.update(fs.readFileSync(path.join(root, `${safe}.layers`)).subarray(0, 8192));
302
+ } catch {
303
+ h.update("\0no-layers");
291
304
  }
292
305
  return h.digest("hex").slice(0, 32);
293
306
  } catch {
@@ -4411,5 +4424,11 @@ module.exports = {
4411
4424
  _ociCanonicalRef: ociCanonicalRef,
4412
4425
  _PYC_BUILD_CODE: PYC_BUILD_CODE,
4413
4426
  _pycStartBuild: pycStartBuild,
4427
+ /** Await every build still in flight. FOR TESTS, and it removes a real race rather than masking
4428
+ * one: a test that removes its temp cache home while a background build is still writing into it
4429
+ * fails in `rimraf` with ENOTEMPTY, which is a teardown ordering bug and reads like a product
4430
+ * defect. Measured under the gate, where the machine is busy enough for the build to outlive the
4431
+ * test. */
4432
+ _pycSettle: () => Promise.allSettled([...PYC_BUILDS.values()]),
4414
4433
  version: VERSION,
4415
4434
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kern-sandbox",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "description": "Your model writes the code. This runs it where it can't touch your machine: a rootless Linux container, no daemon, no VM, no cloud, no account. A kernel boundary, not a microVM: for deliberately hostile code, use one.",
5
5
  "keywords": [
6
6
  "sandbox",