Features: state the honest boundary a member runs inside (#184)

Several settings on this page look like security controls. They are not, and
the reason is the same for all of them: a member runs as the same OS user as
the lead. That fact was written nowhere an operator reads, so people read the
settings and concluded a member was contained.

Adds one entry giving the per-channel truth, the proven path a member takes to
the operator's ssh key, and the point that no meaningful boundary exists inside
one uid. Names the recurring shape: a gate written after an incident closes
only the direction that incident came from.
Dai Ha
2026-09-03 16:44:51 +07:00
parent 573eed33fb
commit fd2832b6d2
+38
@@ -3113,6 +3113,44 @@ describes the wrong account and only the configured `memberLoginShell:` is read.
`memberHerdrSocket` and leave `memberLoginShell` unset, the shell reads as `<unset>`, counts as
non-zsh, and **every spawn is refused**. The refusal message says so, but it is cheaper to know first.
## What a member can actually reach — the honest boundary
**What.** This is not a feature. It is the frame every credential setting on this page sits inside,
and it has been missing, so people have read those settings and concluded more than the settings say.
**A member runs as the same OS user as the lead. That is a trust model, not a sandbox.** Inside one
uid, ordinary Unix file permissions give no confidentiality boundary. A member that can run commands
can read any file this user can read.
What each control actually does:
| Channel | Control today | What it really buys |
|---|---|---|
| Environment | `memberCredentials` + the `ZDOTDIR` scrub | **Real**, for what the member *inherits*. It does not protect the values — the member can source the same files again. |
| Files | worktree provisioning neutralizes some config files | **Not a boundary.** File modes do not separate processes with the same uid. A member can read the original by absolute path. |
| Sockets | `sshAuthSock: block` | **Not a boundary.** It omits one variable. It does not revoke access to the socket, and it cannot remove a readable key file. |
| Git config | the worktree's HTTPS rewrite + cleared `credential.helper` | **Routing, not enforcement.** Normal git commands go the intended way; a member can still call `ssh` directly. |
| Process table | secrets kept out of argv | **No boundary.** argv is visible to any local user, and a different uid would not fix that. |
**Why it exists.** The proven path is short: the scrub blanks `SSH_AUTH_SOCK` → git starts ssh → ssh
reads the user's config → it opens an `IdentityFile` that is readable and has no passphrase → the
forge accepts the operator's key. Measured on 2026-08-28: a member with the socket blanked pushed
successfully. The gate closes lead → child environment inheritance. It never closed member →
filesystem → credential.
That is worth naming as a shape, because it keeps recurring here: **a gate written after an incident
closes only the direction that incident came from.** Ask which states open it, not just which it
blocked.
**The gotcha: no meaningful boundary exists inside one uid.** Environment scrubbing, worktrees and
config rewrites reduce **accidents**, and that is worth having. They do not contain a member that
chooses to look. A real boundary needs a different OS user (`memberHerdrSocket`, above) or OS-level
confinement such as a container or VM — a different execution model, not a longer name list. Even a
different user does not protect argv, and does not protect shared git objects where `worktreeGroup`
grants access to them.
Open work, ranked, is on #184.
## `leadSeats` reports the seat the lead itself holds
**What.** For a `subscription: true` profile, `fleet_list`'s row carries a `leadSeats` field when the