kern-sandbox 0.2.14 → 0.2.15

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 (2) hide show
  1. package/index.js +35 -29
  2. 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.14";
39
+ const VERSION = "0.2.15";
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
- const markers = [
1109
- "kern:",
1110
- "error: pull:",
1111
- "error: curl failed:",
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
- const s = line.replace(/^\s+/, "");
1133
- if (isKernDiagnostic(s) || kernReportedOom(s)) continue;
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
  }
@@ -2813,7 +2810,13 @@ class Kernel {
2813
2810
  `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
2811
  rc,
2815
2812
  ];
2816
- if (looksLikeStartupFailure(err)) return ["startup_failed", "the kernel box failed to start", rc];
2813
+ // AND THE BYTE DECIDES WHETHER THE TEXT IS BELIEVED. kern writes the teardown payload only for a box
2814
+ // that existed, so `kernWrotePayload` is positive proof that this kernel STARTED and a
2815
+ // `startup_failed` here could only be a cell printing kern's prefix at column 0 and dying of
2816
+ // something else. The one-shot path gets this from `_runOne`, which drops the verdict when the start
2817
+ // byte is set; this path had no such guard, so the widened predicate gets it here.
2818
+ if (!kernWrotePayload && looksLikeStartupFailure(err))
2819
+ return ["startup_failed", "the kernel box failed to start", rc];
2817
2820
  if (capSignal === 2)
2818
2821
  return [
2819
2822
  "killed",
@@ -3249,7 +3252,10 @@ class WarmBox {
3249
3252
  if (oomVerdict(oomSignal, err, boxStarted)) {
3250
3253
  type = "oom";
3251
3254
  dflt = "the box exceeded its memory cap and was OOM-killed";
3252
- } else if (looksLikeStartupFailure(err)) {
3255
+ } else if (!boxStarted && looksLikeStartupFailure(err)) {
3256
+ // `!boxStarted` for the reason `_kernelDeathFault` states: kern writes the teardown payload only
3257
+ // for a box that existed, so with the byte set this THROW would be a cell's own column-0 line
3258
+ // deciding that the box never came up.
3253
3259
  throw new SandboxError(err.trim() || "the box failed to start");
3254
3260
  } else if (capSignal === 2) {
3255
3261
  dflt =
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kern-sandbox",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
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",