diff --git a/docs/plans/2026-11-10_accounts-design.md b/docs/plans/2026-11-10_accounts-design.md index 4107f4b21..69f775dc8 100644 --- a/docs/plans/2026-11-10_accounts-design.md +++ b/docs/plans/2026-11-10_accounts-design.md @@ -881,6 +881,43 @@ OIDC session or an account. The rule: never on a deployed one. - Dev mode keeps every CLI verb as it is. +### Host trust boundary + +The host is the trust boundary. The instance uid can read and write the +instance's config, data root, bus files and credentials. That makes the uid +equivalent to root for this instance, though not root on the host. A CLI +token cannot protect the instance from someone who already has that uid: +they can read the token file, the bus database and every key, or change the +files directly. A token could help a remote admin CLI that has no host +access. That is a possible later feature, not part of Phase M. + +Phase M reduces who holds the instance uid and makes local account actions +visible. A dedicated instance uid is used by no agent seat or human login. +In M2, the accounts loader refuses an accounts file with the wrong owner or +group/other write permission; `mosaic accounts` refuses when its uid does +not own the config directory and writes the OS uid into its receipt. In M3, +stamped builds and `.deployed` refuse dev flags. Case 2 runs agent processes +in containers, limiting their access to the host. + +The install paths do not yet establish the dedicated-uid rule. Case 1's +acceptance script takes `--user` and `--home` from its arguments +(`scripts/case1-accept.sh:102-116`) and checks that the Console and bus units +run as that user (`scripts/case1-accept.sh:457-469`). Case 2's install runs +as a supplied non-root user (`scripts/case2-install.sh:2-3,95-103`); its +unit renderer accepts an existing non-root `--user` without checking that +it is dedicated +(`scripts/case2-unit.sh:136-145`) and writes `User=$USER_NAME` +(`scripts/case2-unit.sh:514`). Neither path checks that this account is +non-login or unused by agent seats. In particular, Case 2 can use a human's +own uid today. This is a deployment gap, not a property of the accounts +checks. M3 follow-up 1 closes it by making both install paths require a +dedicated non-login uid, verify config and data-root ownership, and reject +an uid assigned to an agent seat. + +The residual risk is accepted: host root, or the instance uid, owns the +instance. Accounts protect users from one another through the Console and +bus. They do not protect an instance from its host owner. + ### Locks Three locks exist after this design: the per-org lifecycle lock @@ -1295,6 +1332,7 @@ Rollback keeps OIDC. Nothing in it removes `--sso` or turns sign-in off. | Identity confusion across providers | Identity is `(provider, sub)` with the issuer from config. Email matches invites only. A subject belongs to one account. | | Local CLI acting outside the check | In accounts mode `mosaic org` and `decide --org` refuse. `mosaic accounts` runs as the instance uid, which can already read every bus file, and has no rights verbs. | | Tampering with accounts.json on disk | The file must belong to the instance uid and not be group or other writable. Anyone who can write it already runs as the instance user and can read every bus. That is outside this design, as it is for access.json. | +| Host root or the instance uid bypassing accounts | Accepted residual risk: either can read and change the instance config, data root, bus files and credentials. Phase M limits and records who holds the instance uid; it does not make that uid untrusted. | Not covered: a compromised identity provider, a stolen session cookie before its 1 hour idle limit, and a compromised Console process, which holds @@ -1397,6 +1435,10 @@ Acceptance tests, against a fake OIDC provider on port 0: - `--dev-local-owner` from a checkout refuses with accounts.json present, with `console.sso`, and with `.deployed`. - `webui-service.sh render` and `case2-unit.sh` refuse a dev flag. +- Case 1 and Case 2 install tests reject a login-capable uid or an agent-seat + uid as the instance uid, require it to own the config directory and data + root, and verify that the Console and bus units run as that same dedicated + uid. A designated non-login instance uid passes. - The existing sso and dev-owner suites stay green. Grant needed: `stack-copy.sh` writes a new file into every copy, and the