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

Apps, previews, terminals and checks run in a cell of their own #680

Merged opened this in answer to

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

Merged

Closes #658

shipped production is serving it for issue #658 — its issue is already done deploy 8e3d756

opened this

Closes #658

Work in progress: the first cut is pushed so the crew does not pick the same issue. Description follows when the Linux runs are in.

commented

Closes #658

Apps, previews, app terminals, and every install, test and check step now run in a cell (src/apps/contain.ts): bubblewrap, the same tool that holds the crew's shell.

What a cell is

  • Files: the machine is read-only. The forge's data directory and home look empty. /tmp belongs to the cell (256 MiB at most). The only writable places are the cell's own checkout, its environment's data, and a home kept beside it.
  • Processes: the cell has its own pid namespace, so no other process (and no other process's environment) is visible.
  • Network: the cell has its own network namespace with loopback only. The way out is the forge's proxy (src/apps/egress.ts). It reaches the forge and the hosts forge.json declares in the new top-level egress (install, test and check steps also reach registry.npmjs.org). It refuses anything else with a 403 that names the host. A declared name that resolves to a private, loopback or link-local address is refused. The way in, for an app, is a relay from the port the forge proxies to.
  • Limits: memory, CPU (averaged over 30 s) and disk are watched from outside, because a container cannot create cgroups. A cell over a limit is stopped. The reason becomes the step's error (check stopped: it used 600 MiB of memory, over its 512 MiB limit) or the app's last log line. Defaults: an app gets 512 MiB, 1 core and 1 GiB; a step gets 2 GiB, 2 cores and 4 GiB. app.limits and check.limits can change them, capped by the machine.
  • Where the host cannot hold a cell (no bubblewrap, macOS), code runs as before. Each deploy and check run page says "Uncontained", with the reason, using the same run columns and line as the crew's sandbox note.

What existing apps must declare

  • The scaffolded apps (hello, pong, orbit and the rest) declare nothing; they work unchanged.
  • bazaar calls Stripe from the server, so it needs "egress": ["api.stripe.com"].
  • cs-engine uses the TypeSafe SDK, so it needs "egress": ["api.typesafe.ai"].
  • The forge's own forge.json now declares its check limits (4 GiB, 4 cores), the lab app's limits (2 GiB), and api.anthropic.com for a hosted forge's crew.

Tests (test/contain.test.ts, added to the gate)

  • An app, its terminal, its preview and its check, on a fresh non-admin account, each try to read the forge's database, its key, the data directory, another tenant's app directory and the forge process's environment, and to reach an undeclared host, a stand-in for the cluster, and a declared name on a private address. Each attempt is refused, while the forge itself still answers.
  • Memory, CPU and disk limits each stop a step, and the error names the limit.
  • The proxy forwards an allowed request and a CONNECT tunnel byte for byte, and refuses with the host named.
  • Manifest validation covers egress and limits.

Verified on Linux in a pod with the forge's own confinement (user namespace, forge-sandbox profiles):

  • With cells off, the end-to-end probe reaches every target. With cells on, every one is refused.
  • contain, apps, apps-adversarial, apps-busy, apps-sleep, apps-token, checks, crew-sandbox and shards pass with cells on.
  • The new tests also pass nested inside a cell, as the forge's own check of itself would run them.
  • apps-ws (not in the gate) fails the same 4 tests with cells on or off on that loaded node.

Gate on macOS: tsc is clean (this also fixes the service type error in test/apps.test.ts on main). 3 failures, all of which also fail on main: check-list (branch_tell.test.ts and forge-cloudflare-celld.test.ts are in neither list), the crew test's /private path on macOS, and studio #230.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs