docs: clarify host trust boundary

This commit is contained in:
jason.woltje committed 2026-11-10 13:54:23 -06:00
1 parent fe820d17bf
commit 89848a2fa1
1 file changed
+52 -26
+52 -26
View File
@@ -891,28 +891,43 @@ 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.
Phase M narrows who is assigned the instance uid. In the current managed
host setup, both Case 1 and Case 2 use `mosaic`: the host run sheet creates
it as a system account with a non-login shell and no supplementary groups
or sudo rule (`docs/guides/host-run-sheet.md:19-26`). Case 1 acceptance
checks that the Console and bus run as the configured `--user`
(`scripts/case1-accept.sh:102-116,457-469`). Case 2's unit renderer uses
its `--user` for both services (`scripts/case2-unit.sh:136-145,514`); the
host setup runs that path as `mosaic`
(`docs/plans/2026-10-20_case2-rows.md:1197-1200`). These are today's
configured identities, not an installer guarantee: `case2-unit.sh` accepts
any existing non-root user with a matching home. The run sheet, rather than
the renderer, selects the dedicated account.
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.
There is also a separate Console path. `webui-service.sh` installs a
systemd user unit, then asks the operator to start it with
`systemctl --user` (`scripts/webui-service.sh:1-7,19,413-418`). That Console
runs as the account whose user manager starts it, not as the managed
`mosaic` service account. It is a single-user development path, not a safe
accounts-mode deployment path. M3 follow-up 1 is to refuse a deployed or
accounts-mode Console service through this user-unit path.
The stack's own managed agent sessions run as the instance uid inside a
bubblewrap sandbox, not in containers (`scripts/case1-accept.sh:480-496`,
`docs/plans/2026-10-20_case2-rows.md:1031-1034,1197-1199`). The sandbox
limits their filesystem view. A sandbox escape has the same authority as
holding the instance uid. This is different from a development agent seat:
Phase M requires the instance uid to differ from every human login and
development seat uid. A non-login shell prevents direct login, but root or a
human with sudo or equivalent authority can still act as `mosaic`.
M2's accounts loader refuses a file with the wrong uid or group or other
write permission. M2 does not currently specify the config-directory owner
check for `mosaic accounts` or an OS-uid receipt. M3 pins both as integration
expectations. The recorded uid identifies the process, not
necessarily the person who invoked it. The receipt is writable by the
instance uid and is not tamper-evident against that uid. Stamped builds and
`.deployed` also refuse dev flags in M3.
The residual risk is accepted: host root, or the instance uid, owns the
instance. Accounts protect users from one another through the Console and
@@ -1332,7 +1347,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. |
| 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. An administrator who can become the instance uid has the same authority. An OS-uid receipt identifies the process, not the human, and the instance uid can change the receipt. |
Not covered: a compromised identity provider, a stolen session cookie
before its 1 hour idle limit, and a compromised Console process, which holds
@@ -1435,10 +1450,21 @@ 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.
- `case2-unit.sh` accepts the host run sheet's `mosaic` account fixture only
when its shell is non-login, its primary group has no other members, and
it owns the config directory and data root. Fixtures for a human login and
a development seat with interactive shells are refused. Both rendered
system units use that same uid.
- `case1-accept.sh` F1 continues to verify that the running Console and bus
processes have the configured instance uid.
- M3 integration tests verify that `mosaic accounts` refuses when its
effective uid does not own the config directory and that its receipt
records the effective OS uid. The test documents that this is process
identity, not human attribution or tamper evidence.
- **M3 follow-up 1:** accounts mode refuses the `webui-service.sh` systemd
user-unit path. Managed accounts instances start through the system units
rendered by `case2-unit.sh`. Tests show the user-unit path cannot start a
deployed accounts instance while development mode remains available.
- The existing sso and dev-owner suites stay green.
Grant needed: `stack-copy.sh` writes a new file into every copy, and the