docs(chord): the terminal's sandbox is a child process

This commit is contained in:
Mario Zechner 2026-11-02 20:26:39 +00:00
parent 342514bdf8
commit 128912d246

View file

@ -2299,7 +2299,8 @@ validation and copies the v2 kernel added around Delta.
`useHold(Type, params)`, `useWatch(watchable)`, `useRegistry(R)`, `useRefState(ref)`; releasing holds on unmount, `useHold(Type, params)`, `useWatch(watchable)`, `useRegistry(R)`, `useRefState(ref)`; releasing holds on unmount,
and a component unmounted by a reload never counts as leaving (design §21). No components, no styling; non-React apps and a component unmounted by a reload never counts as leaving (design §21). No components, no styling; non-React apps
never load it. never load it.
- **Slice 11, P8: the terminal client always runs sandboxed (owner, 2026-11-01: "the terminal should always use the - **Slice 11, P8: the terminal client always runs sandboxed (**superseded in part** by "The terminal's sandbox is a
child process", 2026-11-02: no longer a worker thread) (owner, 2026-11-01: "the terminal should always use the
sandbox approach"):** `chord/next/tui` is a Node client host that connects with a token (decision "The app kit, point sandbox approach"):** `chord/next/tui` is a Node client host that connects with a token (decision "The app kit, point
3", (c)), downloads the base bundle and the TUI plugin scripts from the server and runs the app's TUI entry, always 3", (c)), downloads the base bundle and the TUI plugin scripts from the server and runs the app's TUI entry, always
inside p2's sandbox: a permission-dropped worker thread with a bridge terminal, the trusted launcher outside it inside p2's sandbox: a permission-dropped worker thread with a bridge terminal, the trusted launcher outside it
@ -2356,6 +2357,22 @@ validation and copies the v2 kernel added around Delta.
restarts it. Usually nothing runs: Radius receiving uploads runs no host for a session it does not drive, and taking restarts it. Usually nothing runs: Radius receiving uploads runs no host for a session it does not drive, and taking
a session back pulls before its worker starts. A live view of a replica (watching a session continue elsewhere) is a a session back pulls before its worker starts. A live view of a replica (watching a session continue elsewhere) is a
later refinement: fewer pulls while watched, or a read-only view without the worker. later refinement: fewer pulls while watched, or a read-only view without the worker.
- **The terminal's sandbox is a child process (owner, 2026-11-02: "yes"; refines "Slice 11, P8"; slice 11 recheck,
Opus E1, E2):** the hardened worker thread was escaped twice in one review round, because downloaded code shares
one JavaScript realm with Node's internals: E1 replaced `Set.prototype.has`, which the `require` allowlist used, and
`require("tty")` wrote raw to the terminal; E2 patched `EventEmitter.prototype.emit` and caught Node's real
`process` when Node emitted a warning on it (environment, `kill`, every builtin). Each fix closes one road and the
next reachable internal is another. So the sandbox is a child process of the launcher's Node binary: stdin, stdout
and stderr are pipes to the launcher (no descriptor is the terminal), its environment empty (only
`LD_LIBRARY_PATH` copied when the launcher has one: needed to run the same binary, set by the user), its directory
a private empty one, `--permission` with nothing granted (no file system, network, child processes, workers,
addons, inspector, WASI), `--no-experimental-sqlite` (SQLite opens files past the permission model, Node gist
https://gist.github.com/badlogic/a83cd80d0669dbcf77e029ac7b25ac96). Messages are length-prefixed binary frames on
the pipes, not Node's IPC. The JavaScript layer stays as a second barrier (evaluation only through the bootstrap,
the builtin allowlist, a frozen substitute `process`); the V8 code-generation toggle goes (the bridge evaluates
strings anyway). Known gaps, accepted until Node closes them: the child can signal the user's processes
(`process.kill`) and allocate memory outside its heap. Cost: one more Node process per terminal session (measured
by the build against the worker). Back to a worker only if Node ships per-worker permissions.
- **A plugin's npm dependencies are bundled; external only by opt-in (owner, 2026-10-29: "explicit opt in + (a)"):** - **A plugin's npm dependencies are bundled; external only by opt-in (owner, 2026-10-29: "explicit opt in + (a)"):**
a plugin's `dependencies` are installed into its own `node_modules` (`npm install --ignore-scripts`) and bundled into a plugin's `dependencies` are installed into its own `node_modules` (`npm install --ignore-scripts`) and bundled into
each facet that imports them (host packages, its `peerDependencies`, stay external and are passed in). On Node a each facet that imports them (host packages, its `peerDependencies`, stay external and are passed in). On Node a