kern-sandbox 0.2.9 → 0.2.10

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 +33 -1
  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.9";
39
+ const VERSION = "0.2.10";
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
@@ -446,6 +446,28 @@ const REFUSED_MOUNT_SOURCES = new Set([
446
446
  "/run/docker.sock",
447
447
  ]);
448
448
 
449
+ /** kern's OWN state on this host: [path, what it is]. Refused as a mount source, like the docker socket
450
+ * and for the same reason.
451
+ *
452
+ * FOUND BY A CHECKLIST ROW, measured on the Python binding first: mounting `$XDG_RUNTIME_DIR/kern` was
453
+ * ACCEPTED, which hands the code in the box kern's control plane (the registry, instance dirs, netns
454
+ * handles and exit files of every box this user runs). The image cache is the same class one step
455
+ * removed: a box that writes it poisons the rootfs a LATER box runs. Resolved per call, because these
456
+ * follow the environment and a service manager moves them. */
457
+ function kernStateDirs() {
458
+ const uid = process.getuid();
459
+ const runtime = process.env.XDG_RUNTIME_DIR || `/run/user/${uid}`;
460
+ const home = os.homedir();
461
+ const cache = process.env.XDG_CACHE_HOME || path.join(home, ".cache");
462
+ const config = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
463
+ return [
464
+ [path.join(runtime, "kern"), "kern's runtime state (the registry, instance dirs, netns handles and exit files of every box you are running)"],
465
+ [`/run/user/${uid}/kern`, "kern's runtime state"],
466
+ [path.join(cache, "kern"), "kern's image cache (a box that writes it poisons the rootfs a later box runs)"],
467
+ [path.join(config, "kern"), "kern's configuration (the profiles a later box may be given)"],
468
+ ];
469
+ }
470
+
449
471
  /** Credential directories, refused as a COMPONENT anywhere in the source. The set above is absolute
450
472
  * paths, so it refused `$HOME` and accepted `$HOME/.ssh`: MEASURED, a box mounted with `~/.ssh` listed
451
473
  * `id_ed25519` and `authorized_keys`. Refusing the parent and allowing its most sensitive child is the
@@ -857,6 +879,16 @@ function validateMount(source, target) {
857
879
  `refusing to mount the sensitive host path ${JSON.stringify(real)} into a sandbox ` +
858
880
  "(this would defeat the isolation)",
859
881
  );
882
+ for (const [state, what] of kernStateDirs()) {
883
+ let sreal;
884
+ try { sreal = fs.realpathSync(state); } catch { sreal = state; }
885
+ if (real === sreal || real.startsWith(sreal + path.sep))
886
+ throw new MountRefused(
887
+ `refusing to mount ${JSON.stringify(real)}: it is ${what}. Mounting kern's own state into a box ` +
888
+ "it started gives the code inside the sandbox's control plane, which is the same reason the " +
889
+ "docker socket is refused",
890
+ );
891
+ }
860
892
  for (const part of real.split(path.sep))
861
893
  if (REFUSED_MOUNT_COMPONENTS.has(part))
862
894
  throw new MountRefused(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kern-sandbox",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
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",