Public: anyone can read this; the crew can change it.

Cells in a pod see an allow-list of mounts, not the pod's mounts minus a deny-list #826

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Closes #801

shipped production is serving it for issue #801 — its issue is already done deploy 2fa0c24

opened this

Closes #801

What changes (src/apps/contain.ts)

  • Inside Kubernetes (KUBERNETES_SERVICE_HOST set), a cell sees an allow-list of the pod's mounts. Every mount point in /proc/self/mountinfo is hidden except:

- / and the cell's own /dev, /proc and /sys - /etc/hosts, /etc/hostname and /etc/resolv.conf - any mount the runtime or its tools (sh, bwrap, socat, git, bun) live on - anything named in FORGE_CELL_MOUNTS_KEEP (:-separated)

ConfigMaps, hostPaths, PVCs and CSI volumes mounted for the forge are now empty inside a cell, whatever their filesystem type.

  • Elsewhere, the earlier rule stays: the machine's mounts minus the secret-bearing ones. A developer's machine mounts its own system (/nix/store, a home), and a cell must still run there.
  • FORGE_CELL_MOUNTS=allow-list|deny-list chooses either rule explicitly.
  • From the #766 review: the crew now logs a warning at start when the data directory sits inside the machine's temp directory. In that case the shell can be denied only the forge's own pieces of the temp directory, not all of it.

Tests

  • test/contain.test.ts, on a recorded pod mountinfo with a PVC, a ConfigMap, a hostPath, a CSI (NFS) volume, /nix/store and a secret:

- in allow-list mode, every one of those is hidden, and the root, /dev, /proc, /sys and the resolver's files are kept - a mount the runtime lives on is kept - deny-list mode keeps system mounts and hides secrets - the mode defaults to allow-list in Kubernetes, and the override works both ways

  • test/crew-sandbox.test.ts covers dataInTemp.

Linux probe, in the separate test pod, using a stand-in command (not a model run). The pod mounts a ConfigMap at /etc/app-config, a hostPath at /mnt/host and a PVC at /mnt/pvc, all ext4, plus a secret:

path inside a celldeny-list (before)allow-list (now, default in a pod)
/etc/app-config/settingREADABLErefused
/mnt/host/fileREADABLErefused
/mnt/pvc/fileREADABLErefused
/etc/dummy-key, /run/secrets/dummy/keyrefusedrefused

In both modes:

  • bun runs and git --version answers.
  • The forge answers through the loopback relay.
  • /etc/resolv.conf is present.

In the same pod with the allow-list active, test/contain.test.ts passed (25/25) and apps-busy passed. checks failed one test ("a newer commit supersedes…"), which had also failed with cells off on that node earlier, so it is load.

Gate on macOS: passes except crew.test.ts "assignment" (the known /private path on macOS, failing on main too).

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs