kern-sandbox 0.1.12 → 0.1.13
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 +2 -2
- package/index.js +37 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**[kern](https://github.com/getkern/kern)** is a fast, rootless sandbox and virtual resource
|
|
4
4
|
runtime for any workload, including untrusted and AI-generated code: a real, kernel-enforced box
|
|
5
|
-
that starts in **3.
|
|
5
|
+
that starts in **3.4 ms** from an OCI image, out of one **~1.8 MB** binary, with no daemon.
|
|
6
6
|
**kern-sandbox**
|
|
7
7
|
is its Node / TypeScript binding: run untrusted or agent-generated code in a fresh, isolated box, from Node.
|
|
8
8
|
|
|
@@ -177,7 +177,7 @@ can still WRITE an artifact to the workspace and `readFile` it if you prefer.
|
|
|
177
177
|
**Warm kernel (kill the interpreter boot).** Each `runCode` starts a **fresh** interpreter, paying the
|
|
178
178
|
CPython boot (~12 ms) every call. When you run many cells that share state (a REPL, a notebook, an
|
|
179
179
|
agent's tool loop), open a `kernel()`: ONE warm interpreter in a long-lived box, fed cells over a pipe.
|
|
180
|
-
In-memory state persists across cells and the per-cell cost drops from ~
|
|
180
|
+
In-memory state persists across cells and the per-cell cost drops from ~14 ms to **sub-millisecond**
|
|
181
181
|
(~300x). Same rich `results` capture as `runCode`.
|
|
182
182
|
|
|
183
183
|
```js
|
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.1.
|
|
39
|
+
const VERSION = "0.1.13";
|
|
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
|
|
@@ -968,19 +968,16 @@ class Sandbox {
|
|
|
968
968
|
const err = cappedCollector(child.stderr, this.maxOutputBytes, cbErr);
|
|
969
969
|
let timedOut = false;
|
|
970
970
|
let settled = false;
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}, timeoutS * 1000);
|
|
976
|
-
|
|
977
|
-
// Hard safety net: a CPU-bound box can survive our signals until kern's backstop reaps it; never
|
|
978
|
-
// hang the caller. If close hasn't fired a few seconds after our teardown, resolve anyway.
|
|
971
|
+
// Both timers are armed further down, once `finish` exists. They are declared here, as `let`,
|
|
972
|
+
// so the closures that clear them can never reference a binding in its temporal dead zone
|
|
973
|
+
// whatever the callback ordering turns out to be.
|
|
974
|
+
let timer = null;
|
|
979
975
|
let hardTimer = null;
|
|
976
|
+
|
|
980
977
|
const finish = (code, signal) => {
|
|
981
978
|
if (settled) return;
|
|
982
979
|
settled = true;
|
|
983
|
-
clearTimeout(timer);
|
|
980
|
+
if (timer) clearTimeout(timer);
|
|
984
981
|
if (hardTimer) clearTimeout(hardTimer);
|
|
985
982
|
// kern has read the file by the time it exits; leaving it behind would accrete one per call
|
|
986
983
|
// in a persistent `workspace`.
|
|
@@ -1008,20 +1005,22 @@ class Sandbox {
|
|
|
1008
1005
|
child.on("close", (code, signal) => {
|
|
1009
1006
|
finish(code, signal);
|
|
1010
1007
|
});
|
|
1011
|
-
//
|
|
1008
|
+
// Hard safety net: a CPU-bound box can survive our signals until kern's backstop reaps it;
|
|
1009
|
+
// never hang the caller. If close hasn't fired a few seconds after our teardown, resolve anyway.
|
|
1012
1010
|
const armHardNet = () => {
|
|
1013
1011
|
if (hardTimer) return;
|
|
1014
1012
|
hardTimer = setTimeout(() => finish(EXIT_SIGKILL, "SIGKILL"), 10000);
|
|
1015
1013
|
};
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1014
|
+
|
|
1015
|
+
timer = setTimeout(() => {
|
|
1016
|
+
timedOut = true;
|
|
1017
|
+
this._teardown(child, name, childEnv);
|
|
1018
|
+
// Armed HERE, at the one place that decides to kill. It used to be noticed instead by a
|
|
1019
|
+
// 250 ms setInterval that `finish` never cleared, so after every call that interval kept the
|
|
1020
|
+
// event loop alive until its own next tick: measured 224 to 232 ms of dead time between a
|
|
1021
|
+
// call resolving and the process being able to exit, against 19 to 27 ms of real work.
|
|
1022
|
+
armHardNet();
|
|
1023
|
+
}, timeoutS * 1000);
|
|
1025
1024
|
});
|
|
1026
1025
|
}
|
|
1027
1026
|
|
|
@@ -1726,7 +1725,24 @@ class Kernel {
|
|
|
1726
1725
|
} catch {
|
|
1727
1726
|
/* ignore */
|
|
1728
1727
|
}
|
|
1729
|
-
|
|
1728
|
+
// Wait for the exit EVENT, capped at 150 ms, rather than sleeping 150 ms unconditionally: that
|
|
1729
|
+
// fixed sleep cost 152 ms on every close of a persistent kernel (measured) for a box that
|
|
1730
|
+
// exits in a few. A child that is already gone has emitted `exit` and will not emit it again,
|
|
1731
|
+
// so that case is tested directly instead of waited on.
|
|
1732
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
1733
|
+
await new Promise((resolve) => {
|
|
1734
|
+
let t = null;
|
|
1735
|
+
const onExit = () => {
|
|
1736
|
+
if (t !== null) clearTimeout(t);
|
|
1737
|
+
resolve();
|
|
1738
|
+
};
|
|
1739
|
+
t = setTimeout(() => {
|
|
1740
|
+
child.removeListener("exit", onExit);
|
|
1741
|
+
resolve();
|
|
1742
|
+
}, 150);
|
|
1743
|
+
child.once("exit", onExit);
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1730
1746
|
this._kill();
|
|
1731
1747
|
} else {
|
|
1732
1748
|
this._kill();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kern-sandbox",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "kern is a fast, rootless sandbox and virtual resource runtime for any workload, including untrusted and AI-generated code; kern-sandbox is its Node/TypeScript binding. Run untrusted or agent-generated code (Python/JS/Bash) in a real, kernel-enforced box in single-digit milliseconds, with no cloud, no account and no VM.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sandbox",
|