Features: sshAgentEnv: omit — the ssh-agent setting is named after what it does (#266)

Dai Ha
2026-09-04 08:28:02 +07:00
parent ca30a62027
commit 6a22c4ba15
+38 -4
@@ -1776,8 +1776,9 @@ has its own once-per-daemon guard, so a benign INFO can no longer suppress a ser
**`SSH_AUTH_SOCK` is now blocked, and that is a downgrade, not a win.** It was once allowed on
purpose, because a worktree's remote was `ssh://git@git.ltms.dev` and a member could not push
without the agent socket. Members now push over HTTPS with `WORKER_GITEA_TOKEN`, so the live config
sets `sshAuthSock: block`, and `MemberEnvAllowList.derive` drops the name even if an operator lists
it under `allow:` — the config cannot re-grant it by accident.
sets `sshAgentEnv: omit` (spelled `sshAuthSock: block` before #266; both still parse), and
`MemberEnvAllowList.derive` drops the name even if an operator lists it under `allow:` — the config
cannot re-grant it by accident.
Do not read that as the problem being solved. #184 measured a member with the socket blanked pushing
**fine anyway**: the forge key is a readable, passphrase-free *file*, and `ssh -G` finds it outside
@@ -1800,7 +1801,7 @@ anything that re-exports them afterwards silently undoes it.
```yaml
memberCredentials:
policy: allow-list # default is "deny-by-default"
sshAuthSock: block # "allow" only if a member must use the operator's ssh-agent
sshAgentEnv: omit # "inherit" only if a member must use the operator's ssh-agent
```
**Why it exists.** A control that lives inside a sourced file can always be undone by a file sourced
@@ -3128,7 +3129,7 @@ What each control actually does:
|---|---|---|
| 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. |
| Sockets | `sshAgentEnv: omit` | **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. |
@@ -3152,6 +3153,39 @@ grants access to them.
Open work, ranked, is on #184.
## The ssh-agent setting is named after what it does — `sshAgentEnv: omit`
**What.** `memberCredentials.sshAuthSock: block|allow` is now
`memberCredentials.sshAgentEnv: omit|inherit`.
**The knob.** All eight spellings parse, silently, with no deprecation warning:
```yaml
memberCredentials:
sshAgentEnv: omit # canonical; "inherit" passes SSH_AUTH_SOCK through
# sshAuthSock: block # the old key and old values still work, unchanged
```
Old configs need no edit. If both keys appear, `sshAgentEnv` wins. **An unrecognised value
normalises to `omit`**, so a typo fails closed rather than handing a member the agent.
**Why it exists.** `block` named an effect fleetd does not have. It omits the variable from the
member's environment; it does not deny access to the socket. A member runs as the same OS user, so
the socket stays reachable and the path is discoverable. An operator scanning a config file reads a
value name and stops — that is the point of a good one — so `block` was actively misleading, and the
honest explanation sat in a comment most readers never reach.
What the setting still buys is real and worth keeping: a member does not pick up the operator's
agent by default, which removes a whole class of accident. It is an accident-reducer, not a deny.
**The gotcha: the shim is load-bearing, not cosmetic.** `fleetd.yaml` is gitignored, so no worker
can see it and no test in the repo covers it. The live config on this host still says
`sshAuthSock: block`. A rename without the read-both shim would have silently dropped the setting at
the next restart — trading a naming bug for a real credential regression, in a file the test suite
cannot reach. This was verified against the running daemon rather than argued: after redeploy,
`GET /member-credentials` still reported `policy: allow-list` with 39 known / 7 allowed / 34 blocked,
and a real member spawned on the new jar reported `SSH_AUTH_SOCK: unset`.
## fleetd states the member trust model at startup
**What.** Every boot, `fleetd` logs one INFO line saying what kind of boundary members run inside.