kern-sandbox 0.2.14 → 0.2.16
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/index.js +43 -30
- package/package.json +1 -1
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.
|
|
39
|
+
const VERSION = "0.2.16";
|
|
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
|
|
@@ -1104,34 +1104,31 @@ function kernReportedOom(stderr) {
|
|
|
1104
1104
|
return false;
|
|
1105
1105
|
}
|
|
1106
1106
|
|
|
1107
|
+
/** The two prefixes kern's CLI writes AT COLUMN 0, which is the whole vocabulary of "kern is speaking".
|
|
1108
|
+
* `kern-cli/src/main.rs` reports every error through one `eprintln!("error: {}", ...)` and
|
|
1109
|
+
* `ui::scrub_message` indents every continuation line so a hostile value inside a message cannot forge a
|
|
1110
|
+
* line at column 0. Mirrors `_KERN_SPEAKING`.
|
|
1111
|
+
*
|
|
1112
|
+
* WHAT THIS REPLACED: a list of eleven message OPENINGS, each added after a caller measured a
|
|
1113
|
+
* `fault: null`. Enumerating the error texts of a binary with hundreds of them behind one printer cannot
|
|
1114
|
+
* be finished, and an external reviewer ended the argument with `image: ""`, whose
|
|
1115
|
+
* `error: bad image reference: empty` was in none of the eleven. */
|
|
1116
|
+
const KERN_SPEAKING = ["error: ", "kern:"];
|
|
1117
|
+
|
|
1118
|
+
/** True iff KERN ITSELF reported an error on this box, rather than the workload failing.
|
|
1119
|
+
*
|
|
1120
|
+
* It does NOT decide forgery from the text: a workload can print `error: anything` at column 0, and no
|
|
1121
|
+
* list of openings ever stopped that. The `KERN_STARTED_FD` byte does, and the callers pair it with this
|
|
1122
|
+
* predicate (`_runOne` drops a `startup_failed` when kern signalled a start; the kernel paths ask the
|
|
1123
|
+
* byte first). Mirrors `_looks_like_startup_failure`. */
|
|
1107
1124
|
function looksLikeStartupFailure(stderr) {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
"error: registry:",
|
|
1113
|
-
"error: manifest:",
|
|
1114
|
-
"error: sandbox:",
|
|
1115
|
-
"error: box:",
|
|
1116
|
-
"error: oci:",
|
|
1117
|
-
"error: image:",
|
|
1118
|
-
// REACHABLE EXACTLY WHEN A CALLER PASSES `profiles`, and missing until it was measured: a profile
|
|
1119
|
-
// name that is well formed but absent from `kern.toml` makes kern refuse before any box exists, and
|
|
1120
|
-
// without this marker the call came back `exitCode 1, fault null`, which a caller reading `fault`
|
|
1121
|
-
// cannot tell from their own code exiting 1.
|
|
1122
|
-
"error: config:",
|
|
1123
|
-
// The binding builds its own argv, so these mean IT got something wrong; either way the box never ran.
|
|
1124
|
-
"error: usage:",
|
|
1125
|
-
"error: invalid box name:",
|
|
1126
|
-
];
|
|
1127
|
-
// The OOM sentence is skipped for a sharper reason than the benign notes: it is a report about a box
|
|
1128
|
-
// that RAN, and it is `kern:`-prefixed, so it used to satisfy this predicate. MEASURED, that is how a
|
|
1129
|
-
// real OOM on a resident kernel came back as `startup_failed` and was THROWN instead of returning an
|
|
1130
|
-
// `oom` fault.
|
|
1125
|
+
// kern's BENIGN lines are subtracted first, which is why this cannot be a bare prefix test: the
|
|
1126
|
+
// posture banner and `warning:`/`note:` carry `kern:` too. The OOM sentence is skipped for a sharper
|
|
1127
|
+
// reason: it is a report about a box that RAN, and MEASURED that is how a real OOM on a resident
|
|
1128
|
+
// kernel came back as `startup_failed` and was THROWN instead of returning an `oom` fault.
|
|
1131
1129
|
for (const line of stderr.split("\n")) {
|
|
1132
|
-
|
|
1133
|
-
if (
|
|
1134
|
-
if (s.includes("sandbox setup failed") || markers.some((m) => s.startsWith(m))) return true;
|
|
1130
|
+
if (isKernDiagnostic(line) || kernReportedOom(line)) continue;
|
|
1131
|
+
if (KERN_SPEAKING.some((m) => line.startsWith(m)) || line.includes("sandbox setup failed")) return true;
|
|
1135
1132
|
}
|
|
1136
1133
|
return false;
|
|
1137
1134
|
}
|
|
@@ -1792,10 +1789,17 @@ class Sandbox {
|
|
|
1792
1789
|
detail = reason || "the box could not execute it";
|
|
1793
1790
|
}
|
|
1794
1791
|
fault = sandboxFault("exec_failed", `'${what}' could not be started in the box: ${detail}`);
|
|
1795
|
-
} else if (
|
|
1792
|
+
} else if (fault && fault.type === "startup_failed" && (boxStarted || stdout.trim())) {
|
|
1796
1793
|
// kern signalled the box STARTED, so a `startup_failed` here is only the stderr heuristic
|
|
1797
1794
|
// matching a marker the WORKLOAD wrote (code-based faults are decided first). The box
|
|
1798
1795
|
// demonstrably ran: this is the workload's own non-zero exit - reclassify to a normal result.
|
|
1796
|
+
//
|
|
1797
|
+
// STDOUT IS THE SECOND WITNESS, and it is here because the first one can be absent. MEASURED
|
|
1798
|
+
// with a KERN_BIN wrapper that closes `KERN_STARTED_FD` before exec'ing the real kern: the box
|
|
1799
|
+
// ran, printed, exited 1 with `error: forged` on stderr, and came back `startup_failed`. The
|
|
1800
|
+
// same hole is open on any kern too old to write the byte. A box that never started cannot
|
|
1801
|
+
// print, and every genuine startup failure measured returns stdout EMPTY, so this is only ever
|
|
1802
|
+
// read as evidence FOR a box having run, never against: a silent workload loses nothing.
|
|
1799
1803
|
fault = null;
|
|
1800
1804
|
}
|
|
1801
1805
|
// A box that FAILED TO START ran no user code, so REJECT rather than resolve a hollow
|
|
@@ -2813,7 +2817,13 @@ class Kernel {
|
|
|
2813
2817
|
`the code crashed: the cell died on signal ${workloadSignal} (${SIGNAL_NAMES[workloadSignal]}), which took the kernel box with it because the interpreter is its PID 1. The sandbox did not act; the next call reopens a kernel`,
|
|
2814
2818
|
rc,
|
|
2815
2819
|
];
|
|
2816
|
-
|
|
2820
|
+
// AND THE BYTE DECIDES WHETHER THE TEXT IS BELIEVED. kern writes the teardown payload only for a box
|
|
2821
|
+
// that existed, so `kernWrotePayload` is positive proof that this kernel STARTED and a
|
|
2822
|
+
// `startup_failed` here could only be a cell printing kern's prefix at column 0 and dying of
|
|
2823
|
+
// something else. The one-shot path gets this from `_runOne`, which drops the verdict when the start
|
|
2824
|
+
// byte is set; this path had no such guard, so the widened predicate gets it here.
|
|
2825
|
+
if (!kernWrotePayload && looksLikeStartupFailure(err))
|
|
2826
|
+
return ["startup_failed", "the kernel box failed to start", rc];
|
|
2817
2827
|
if (capSignal === 2)
|
|
2818
2828
|
return [
|
|
2819
2829
|
"killed",
|
|
@@ -3249,7 +3259,10 @@ class WarmBox {
|
|
|
3249
3259
|
if (oomVerdict(oomSignal, err, boxStarted)) {
|
|
3250
3260
|
type = "oom";
|
|
3251
3261
|
dflt = "the box exceeded its memory cap and was OOM-killed";
|
|
3252
|
-
} else if (looksLikeStartupFailure(err)) {
|
|
3262
|
+
} else if (!boxStarted && looksLikeStartupFailure(err)) {
|
|
3263
|
+
// `!boxStarted` for the reason `_kernelDeathFault` states: kern writes the teardown payload only
|
|
3264
|
+
// for a box that existed, so with the byte set this THROW would be a cell's own column-0 line
|
|
3265
|
+
// deciding that the box never came up.
|
|
3253
3266
|
throw new SandboxError(err.trim() || "the box failed to start");
|
|
3254
3267
|
} else if (capSignal === 2) {
|
|
3255
3268
|
dflt =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kern-sandbox",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
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",
|