diff --git a/COMMS/sidecar-app-store/2026-08-11-per-user-claude-handoff.md b/COMMS/sidecar-app-store/2026-08-11-per-user-claude-handoff.md index d2f623ad..4aa0a9ef 100644 --- a/COMMS/sidecar-app-store/2026-08-11-per-user-claude-handoff.md +++ b/COMMS/sidecar-app-store/2026-08-11-per-user-claude-handoff.md @@ -38,11 +38,15 @@ them. Installed SDK is **0.2.59**; `package.json:31` pins `^0.2.41`. the SDK also needs to reach the spawned process in a way `setpriv` breaks, this whole approach is wrong and the fallback is the earlier plan (member turn as its own process). -Second stale claim, already corrected once: `docs/per-user-linux-accounts.md:230` still says a member's -`claude` "needs only `ANTHROPIC_BASE_URL` pointed at the proxy and no key of its own." The previous handoff -§5 retracted that. **Both passages need editing; neither has been edited.** Left alone deliberately so this -document is the record of what was believed when the code was written, rather than the doc silently agreeing -with the code. +Second stale claim, already retracted once by the previous handoff §5: +`docs/per-user-linux-accounts.md:230` said a member's `claude` "needs only `ANTHROPIC_BASE_URL` pointed at the +proxy and no key of its own." That is backwards — the proxy holds the **owner's** credential +(`sidecar/claude/proxy.ts:7` reads the owner's own `~/.claude/.credentials.json`), so pointing a member at it +spends the owner's account on their turns. + +**Both passages have now been corrected in `docs/per-user-linux-accounts.md`** (same commit range as the code). +The superseded text is in that file's git history. If you are reading this handoff against an older checkout, +the doc is the one that moved. --- diff --git a/docs/per-user-linux-accounts.md b/docs/per-user-linux-accounts.md index 33155b35..33a99dd1 100644 --- a/docs/per-user-linux-accounts.md +++ b/docs/per-user-linux-accounts.md @@ -223,12 +223,28 @@ crossed the ancestor that mattered. - **This is not a sandbox.** A member with a shell is on the machine. They cannot read the owner's files or another member's, and `sudo` is not theirs — but they can run code, see process names, and reach the network. It isolates members from each other and from accidents, not from the host. -- **Agents come later.** `claude-manager.ts` drives turns through `query()` from - `@anthropic-ai/claude-agent-sdk`, which spawns `claude` itself and takes `env`/`cwd` but has nowhere to - put a uid. Dropping privileges has to happen *outside* the SDK, which makes a member's turn its own - process — a change of shape rather than a flag. The credential is not the problem: - `officer-anthropic-proxy` already holds it, so a member's `claude` needs only `ANTHROPIC_BASE_URL` - pointed at the proxy and no key of its own. +- **Agents come later** — but by less than this said. Two claims here were wrong and are corrected on + 2026-08-11; the superseded text is in the git history of this file, and the working state is + `COMMS/sidecar-app-store/2026-08-11-per-user-claude-handoff.md`. + + It said the SDK "has nowhere to put a uid", so dropping privileges had to happen *outside* it, making a + member's turn its own process — "a change of shape rather than a flag". It is a flag: `sdk.d.ts:951` + exposes `spawnClaudeCodeProcess`, documented for running Claude Code "in VMs, containers, or remote + environments", and `node:child_process.spawn` already satisfies the `SpawnedProcess` shape it wants. So the + existing sidecar wraps the CLI spawn in `runAsArgv` per turn and there is no second process to stand up. + + It also said the credential is not the problem because `officer-anthropic-proxy` already holds it, so a + member's `claude` "needs only `ANTHROPIC_BASE_URL` pointed at the proxy". That is backwards. The proxy holds + the **owner's** credential (`sidecar/claude/proxy.ts:7` reads the owner's own + `~/.claude/.credentials.json`), so pointing a member at it spends the owner's account on the member's + turns. Per-user Claude means their own login in their own home, and `setpriv --reset-env` is what makes that + the default rather than something to remember: nothing crosses into their process unless it is written into + the argv. + + What does remain out of scope: **no platform process ever runs as a member.** The agent sidecar needs + `POSTGRES_URL` and the JWT signing secret, so a member-uid process holding them could read every account and + sign a token as the owner — more than their shell can do, and already refused by `assertSecretsClosed`. The + harness stays the service user's; only `claude` itself drops privileges. - **`pty`, `vault` and `opencode` receive no identity at all** (`TODO.md` → Multi-user). pty keys purely on a `sessionId` from the query string, and its `/_officer/sessions` endpoints list and kill *every* session on the box. Safe today only because terminal is owner-only. **The moment a member has a shell