ADR 0266: on the control node agents run under an account of their own
The operator chose this remedy for ADR 0259 section 8: an agent there could become root through the operator account's sudo and docker group, and read the tool runner's credential. Record the node fact, the node-engine's judgement the self-check reads, the guard, and how a session starts; issue 333 holds the measured symptom and to-be 36 the design.
This commit is contained in:
@@ -214,6 +214,10 @@ and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 02
|
||||
against this account's home and owned by it
|
||||
([ADR 0181](../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)).
|
||||
Not a name a manifest carries.
|
||||
- **agent account** — the login every coding agent on a node runs as, when the node names one of its own;
|
||||
stated on the node record at the controller's terminal, never by a verb or a setting. Empty means agents
|
||||
run as the operator account. It cannot become root without a person, and the node-engine judges that on
|
||||
every look ([ADR 0266](../02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md)).
|
||||
- **account group** — a group of the machine's group database that a module puts an account in, by
|
||||
declaring the account with that group and nothing else of it. Several modules may each add one to the
|
||||
same account; the mesh takes back only an account group it added
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: accepted
|
||||
date: 2026-10-08
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md
|
||||
---
|
||||
|
||||
# 266. On the control node, agents run under an account of their own that cannot become root without a person
|
||||
|
||||
## Context
|
||||
|
||||
Every coding agent on the mesh runs as the node's operator account
|
||||
([ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)). The tool runner runs
|
||||
as the same account ([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)),
|
||||
and so does the `claude-code` module that configures the agent
|
||||
([to-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md)). The acting tools reach root through
|
||||
that account's passwordless `sudo`, which the `sudo` module declares
|
||||
([to-be 42](../03-DESIGN/01-to-be/42-the-machines-modules-in-order.md)).
|
||||
|
||||
On 2026-10-08 the `sudo` module's own check on the control node said what that account may do:
|
||||
|
||||
- two rules grant it every command as root without a password: the module's drop-in, and a line set by hand
|
||||
in the machine's main sudoers file before the module existed;
|
||||
- it is in the `docker` group, which runs a container with the machine's root filesystem mounted, so it is
|
||||
root without sudo at all;
|
||||
- the tool runner's bus credential is owned by it, mode 0600, and carries the grants of every module the
|
||||
runner serves.
|
||||
|
||||
So any agent on the control node can become root without a person, and can read and use the runner's bus
|
||||
credential. ADR 0259 (hq pull request 211, not yet merged)
|
||||
§8, as revised after the security review of 2026-10-08, rests the router's approve warrants and a channel's
|
||||
`verified-sender` on a self-check that finds **no account an agent runs as can become root without a
|
||||
person**. On the control node that check fails today, and nothing measures it.
|
||||
|
||||
On 2026-10-08 the operator chose one of the three remedies ADR 0259 §8 offered: **on the control node, agents
|
||||
run under an account of their own, without sudo. The operator's own account keeps its sudo.**
|
||||
|
||||
[Issue 333](../04-ISSUES/333-an-agent-on-the-control-node-can-become-root-without-a-person/00-report.md) records
|
||||
the symptom.
|
||||
|
||||
## Considered Options
|
||||
|
||||
### Where the account is named
|
||||
|
||||
1. **A setting of the `claude-code` module.** Rejected. Any principal granted `mesh-controller.settings` may
|
||||
write a setting, agents included (the same argument ADR 0259 §7 makes for the operator's identity). An
|
||||
agent could name the operator's account again.
|
||||
2. **A fixed name in the module's manifest.** Rejected. A definition names no installation
|
||||
([ADR 0155](0155-a-definition-names-no-installation-and-how-that-is-checked.md)), and a fixed name would
|
||||
apply on every node, including the operator's own machines, where the agent works in the operator's
|
||||
desktop and files.
|
||||
3. **A node fact, stated by the operator at the controller's terminal, beside the operator account.**
|
||||
Chosen. No verb and no setting writes it, so no agent can change it.
|
||||
|
||||
### How the operator starts a session there
|
||||
|
||||
1. **`su` to the account.** Rejected. It needs a password for the account: a secret to keep, and an account
|
||||
that a stolen password logs into.
|
||||
2. **An ssh login as the account.** Rejected for now. It works from any terminal, but it opens a network
|
||||
login into the account, needs a key placed for it, and is a second door into the account that the guard
|
||||
cannot watch.
|
||||
3. **`machinectl shell <account>@`.** It gives a full login session, and needs root through polkit, so it
|
||||
goes through the operator's sudo anyway. Heavier than the agent needs; the operator may still type it.
|
||||
4. **The operator's own sudo, `sudo -iu <account> claude`, as one command the module places
|
||||
(`claude-agent`).** Chosen. The operator is the person whose sudo opens the account, and nothing else
|
||||
can open it.
|
||||
|
||||
### Which account the tool runner runs as
|
||||
|
||||
1. **A service account of its own.** Rejected for now:
|
||||
- the acting tools reach root through `sudo -n` as the runner's account, so a service account would need
|
||||
the same passwordless sudo, and root would move rather than shrink;
|
||||
- on the operator's machines the runner must be the operator's account to reach the desktop session
|
||||
([to-be 38](../03-DESIGN/01-to-be/38-building-the-operators-machine.md));
|
||||
- on the control node the operator's account is the person's and holds root anyway, so whatever can read
|
||||
the credential as that account can become root regardless.
|
||||
|
||||
What the security review's first finding asks is that **an agent** cannot read the runner's credential.
|
||||
An agent account of its own reaches that, and the node-engine checks it (below).
|
||||
2. **Keep the runner as the operator's account.** Chosen. The credential still carries every co-hosted
|
||||
module's grants. Narrowing it to one credential per bundle is separate work, and ADR 0259 rule 1 already
|
||||
takes the trusted parties (the router and the verified channels) out of the runner onto accounts of
|
||||
their own.
|
||||
|
||||
### How "cannot become root" is known
|
||||
|
||||
1. **Trust the configuration**: the `sudo` module's rule names only the operator's account. Rejected. One
|
||||
machine showed three ways to root on one account, and two of them were not the module's.
|
||||
2. **The node-engine judges the account on every look, and the controller's self-check reads the verdict.**
|
||||
Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. A node may name an agent account
|
||||
|
||||
**A node may name an agent account: the login every coding agent on it runs as.** The operator states it at
|
||||
the controller's terminal, `mesh-controller node agent-account <node> <account> [home]`, and clears it with
|
||||
`--clear`. It is never a verb and never a setting. The controller refuses root and the node's operator
|
||||
account. **Empty means agents run as the operator account**, as before.
|
||||
|
||||
The controller resolves three machine facts from it:
|
||||
|
||||
- `agent-account` is the agent account, or the operator account where none is named;
|
||||
- `agent-home` is that account's home;
|
||||
- `agent-root` is `never` only where the node names an agent account of its own, and empty otherwise.
|
||||
|
||||
The tool runner is given `MESH_AGENT_ACCOUNT` and `MESH_AGENT_HOME` beside the operator's.
|
||||
|
||||
**On the control node the agent account is `agent`.** The other nodes keep their agents as the operator for
|
||||
now. The operator's machines do so because the agent works in the operator's desktop and files there, under
|
||||
the risk ADR 0234 accepts. A server may follow: it is moved by stating the fact.
|
||||
|
||||
### 2. The account is declared and has no way to root
|
||||
|
||||
The `claude-code` module declares the account as a `user` resource named `${machine:agent-account}`, with
|
||||
`root: ${machine:agent-root}`. The mesh creates the account with its home, its own group and no password.
|
||||
No module puts it in a group. The `sudo` module's rule names only the operator account.
|
||||
|
||||
### 3. The node-engine judges `root: never` on every look
|
||||
|
||||
The node-engine judges a `user` with `root: never` on every look, by reading only. It is a way to root when
|
||||
the account:
|
||||
|
||||
- has uid 0;
|
||||
- is in a group that grants root by membership: `root`, `wheel`, `sudo`, `admin`, `docker`, `disk`, `lxd`,
|
||||
`incus-admin` or `libvirt`;
|
||||
- has any sudo rule, as `sudo -l -U <account>` lists it (any rule, not only one without a password: the
|
||||
decision is no sudo);
|
||||
- can read, by the file's owner, group and mode, any secret the declaration places for another account. The
|
||||
tool runner's credential is one of those.
|
||||
|
||||
Any way found makes the declaring module's account resource unhealthy, with the reason *can become root
|
||||
without a person*, followed by every way found. A question the machine did not answer is unknown, never
|
||||
healthy. The verdict carries `root: never`, so the controller can tell this judgement from a group check.
|
||||
An engine that judges it says so in its statement (contract 3). An older engine is not sent the field,
|
||||
because it parses strictly and would refuse the whole declaration.
|
||||
|
||||
### 4. The controller's self-check says it, and ADR 0259 reads it
|
||||
|
||||
The self-check holds an invariant: every node that names an agent account has it judged, on its engine's
|
||||
newest statement, unable to become root without a person. Otherwise it raises the urgent condition
|
||||
`agent-can-become-root`, which says what to change. `mesh-controller.node` shows the verdict and when it was
|
||||
judged.
|
||||
|
||||
**This is the fact ADR 0259 §8 rule 2 reads.** On a node with an agent account of its own, a healthy verdict
|
||||
means no account an agent runs as can become root without a person. The guard (§6) makes the agent account
|
||||
the only one an agent works in there. On a node without an agent account, agents run as the operator
|
||||
account, and the check holds only where that account cannot become root either. The rule and the code that
|
||||
answers it are one function in the controller, which the router's check will call.
|
||||
|
||||
### 5. The agent is configured for its own account
|
||||
|
||||
The managed directory under `/etc` is unchanged and machine-wide. The licence token, the account the agent is
|
||||
logged in as, an API-key licence and its key-helper, and the items registered at the home scope all go to
|
||||
the agent account's home.
|
||||
|
||||
The tool runner cannot enter that home. So these files are written as the agent account, by a helper: the
|
||||
`claude-code` module's own binary as the managed directory carries it, root's, started through the runner
|
||||
account's sudo. **The escalation goes down only.** The runner's account may become the agent's, and the
|
||||
agent's may become nothing.
|
||||
|
||||
When a node moves its agents, the licence follows at once. What the module placed in the operator's home
|
||||
goes, as [ADR 0182](0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md) allows
|
||||
for what the mesh placed. A login the person made there stays.
|
||||
|
||||
### 6. The guard refuses a session that runs as another account
|
||||
|
||||
On a node with an agent account, the guard on the agent's shell
|
||||
([ADR 0245](0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md))
|
||||
refuses every command and file edit of a session that does not run as that account, whatever it is. No
|
||||
override lifts it. An agent started as the operator out of habit can do nothing there, and is told to use
|
||||
`claude-agent`.
|
||||
|
||||
Its record goes to the machine's journal. The agent account may add to the journal but cannot change it, and
|
||||
it cannot enter the module's state where the record lives elsewhere.
|
||||
|
||||
### 7. How a session starts, and what the agent may still do
|
||||
|
||||
The operator starts a session with `claude-agent`, which runs `claude` as the agent account through the
|
||||
operator's own sudo. Work that needs root is the operator's, in their own shell.
|
||||
|
||||
The agent may do everything the mesh MCP server serves, with the tool runner's grants, as an agent does on
|
||||
any node. It may not run anything as root, use the container runtime, enter the operator's home or read the
|
||||
runner's credential.
|
||||
|
||||
**A tool that runs a caller's command as root, or writes a caller's content into a file root reads, is a way
|
||||
round this record and a defect of that tool**, found by review of its manifest and code.
|
||||
|
||||
The agent account holds a forge key of its own, registered by the operator, so its pushes are its own. It
|
||||
works on branches and pull requests, and a merge needs a person anyway.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The operator types `claude-agent` on the control node instead of `claude`. Typing `claude` by habit gives a
|
||||
session that can do nothing and says why.
|
||||
- The agent's memory and history start empty in the new home. Copying what the operator wants from their own
|
||||
home is the person's act (ADR 0182).
|
||||
- The agent clones into its own home and cannot write the operator's checkouts.
|
||||
- **What got harder:** anything an agent did on the control node through sudo is now a verb, or the
|
||||
operator's act in their own shell. A missing verb is built in the module that owns the area (ADR 0245),
|
||||
never worked round.
|
||||
- **Root that remains:** the operator account keeps its passwordless sudo, and its docker group, so whatever
|
||||
runs as it can still become root: the tool runner and the operator's own shells. No agent runs as it.
|
||||
- **Not judged, said plainly:** a polkit rule granting the account an action, a setuid program other than
|
||||
`sudo`, and a root-run file the account can write. Each is a way to root this check does not find. They
|
||||
are left for a later record, and none is known on the control node.
|
||||
- The tool runner's credential still carries every co-hosted module's grants. The agent cannot read it.
|
||||
- ADR 0181 decided one account per node. This record adds a second, of a different kind: not a person's
|
||||
login, but the one agents run as.
|
||||
- ADR 0259 §8's choice is made. That record can cite this one when it is accepted.
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Rule | Checked by |
|
||||
|---|---|
|
||||
| The agent account is stated only at the controller's terminal, never by a verb or a setting | controller test: the `node` verb's command line is `node show` alone; `node agent-account` refuses root, the operator's account and a malformed name |
|
||||
| The machine facts fall back to the operator account, and `agent-root` is `never` only for an account of its own | controller tests on machine-fact resolution and the tool runner's environment |
|
||||
| An older engine is not sent `root` | controller test: a user resource's `root` is taken out for an engine whose statement says contract 2 or less |
|
||||
| The node-engine judges uid 0, a root group, a sudo rule and a readable secret, before any session logic, and an unanswered question is never healthy | node-engine tests over a fake reader, for each way and for none; a parsing test of `sudo -l -U` |
|
||||
| An agent account that can become root, or is not judged, raises `agent-can-become-root` | controller self-check probe tests: pass, fail, not judged, old engine |
|
||||
| The agent's files are written as its account, and nothing under its home is touched as the runner's | `claude-code` tests: every operation under the agent's home goes through the helper, none outside it does; a helper that died is started again |
|
||||
| The licence follows a move, and what the module placed in the operator's home goes unless a person made or changed it | `claude-code` tests: a binding applied is fetched again when the agent's home holds no token; an access-token-only file goes, a login stays, a changed home item stays |
|
||||
| A session not running as the agent account does nothing there, with no override, and its record is in the journal | `claude-code` guard tests |
|
||||
| End to end | on the control node, after the steps in to-be 36 §9: the self-check passes its new invariant, `claude-agent` starts a session that lists the mesh MCP server's tools and holds its licence, `sudo -n true` in it fails, and `claude` started as the operator is refused at its first command |
|
||||
|
||||
## References
|
||||
|
||||
- ADR 0259 (hq pull request 211, not yet merged) §8
|
||||
(hq pull request 211, revised after the security review of 2026-10-08)
|
||||
- [ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md) (the operator account),
|
||||
[ADR 0182](0182-inside-a-home-the-mesh-owns-what-it-places-and-holds-the-rest-as-found.md) (what the mesh owns
|
||||
in a home), [ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md) (the
|
||||
tool runner as the operator account), [ADR 0245](0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md)
|
||||
(the guard), [ADR 0252](0252-a-module-puts-an-account-in-a-group-and-the-mesh-says-when-a-new-login-is-needed.md)
|
||||
(the node-engine's account judge this extends)
|
||||
- [To-be 36](../03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md) §9, the design this amends
|
||||
- [Issue 333](../04-ISSUES/333-an-agent-on-the-control-node-can-become-root-without-a-person/00-report.md)
|
||||
- the `sudo` module's `sudo_check` on the control node, 2026-10-08
|
||||
@@ -2,8 +2,9 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-catalog modules/claude-code]
|
||||
updated: 2026-10-07
|
||||
updated: 2026-10-08
|
||||
decisions:
|
||||
- 02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md
|
||||
- 02-DECISIONS/0243-the-agent-module-removes-a-home-item-it-did-not-place-only-on-the-persons-word-and-keeps-a-copy.md
|
||||
- 02-DECISIONS/0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md
|
||||
- 02-DECISIONS/0216-the-agents-configuration-is-registered-through-its-module-at-three-scopes-and-served-as-one-plugin.md
|
||||
@@ -282,6 +283,65 @@ An item and its files are one value in the state, refused above 256 KiB.
|
||||
own settings, and the module does not route around that refusal. A settings tool is called on the
|
||||
operator's word.
|
||||
|
||||
## 9. The account the agent runs as
|
||||
|
||||
*Added 2026-10-08 by [ADR 0266](../../02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md).*
|
||||
Everywhere above, "the operator's home" and "the operator account" mean **the account the agent runs as**.
|
||||
That is the operator account, unless the node names an **agent account** of its own. The control node does.
|
||||
|
||||
**The fact.** The operator states the agent account at the controller's terminal. No verb and no setting
|
||||
writes it. The controller resolves the machine facts `agent-account`, `agent-home` and `agent-root`, and gives
|
||||
the tool runner the account and its home. Where the node names none, the facts name the operator account and
|
||||
`agent-root` is empty, so nothing changes there.
|
||||
|
||||
**What the module declares.**
|
||||
|
||||
- A `user` named by `agent-account`, with `root` set from `agent-root`. Where the account is the agent's own,
|
||||
the mesh creates it (home, own group, no password) and the node-engine judges on every look that it cannot
|
||||
become root without a person. That means no uid 0, no group that grants root, no sudo rule, and no secret
|
||||
the mesh placed for another account readable by it.
|
||||
- `~/.claude` under that account's home, owned by it.
|
||||
- `claude-agent`, a command that starts the agent as that account through the operator's own sudo, or
|
||||
directly when already run as it.
|
||||
|
||||
**What the module's code does differently.**
|
||||
|
||||
- The credentials file, the account file, an API-key licence with its key-helper (in a directory of the
|
||||
module's under the agent's `~/.claude`), and the home-scope items go to that account's home.
|
||||
- The tool runner cannot enter that home, so the code touches it through a helper running as the agent
|
||||
account. The helper is the module's own binary, root's, under the managed directory, started through the
|
||||
runner account's sudo. The escalation goes down only.
|
||||
- A binding counts as applied only while the agent account holds its token, so a move takes the licence with
|
||||
it at once.
|
||||
- What the module placed in the operator's home goes: the access-token-only credentials file, and the
|
||||
home-scope items not changed by hand. A login the person made there stays.
|
||||
|
||||
**The guard.** On such a node, the guard refuses every command and file edit of a session not running as the
|
||||
agent account, with no override. Its record goes to the machine's journal.
|
||||
|
||||
**The self-check.** The controller holds an invariant: every node naming an agent account has it judged
|
||||
unable to become root without a person. When it does not hold, it raises `agent-can-become-root`, urgent. ADR
|
||||
0259 §8 reads the same judgement before the router issues an approve warrant.
|
||||
|
||||
**The operator's day on the control node.** The operator works in their own login, with their own sudo, and
|
||||
types `claude-agent` to start an agent. The agent runs in its own home, with its own clones and its own forge
|
||||
key. It reaches the mesh through the mesh MCP server as before, and asks the operator for anything that needs
|
||||
root that no verb serves.
|
||||
|
||||
**Steps on the control node, in this order.**
|
||||
|
||||
1. The node-engine, the controller and this module are built with ADR 0266 and sent to every node. The
|
||||
engines say contract 3, and nothing changes on any node.
|
||||
2. The operator states the agent account on the control node at the controller's terminal. The next send
|
||||
creates the account, places `~/.claude` and `claude-agent`, and moves the licence and the home-scope items.
|
||||
The operator's own account and its sudo are untouched.
|
||||
3. The self-check's new invariant passes, and `mesh-controller.node` shows the account judged.
|
||||
4. The operator starts `claude-agent`, checks that the session lists the mesh's tools and holds its licence,
|
||||
and that `sudo -n true` fails in it. Only then do they stop starting `claude` as themselves.
|
||||
5. The operator generates a forge key in the agent's home and registers it.
|
||||
|
||||
Clearing the fact undoes step 2's routing, and the account is kept (the node-engine never deletes a login).
|
||||
|
||||
## How it is checked
|
||||
|
||||
| Check | Defends |
|
||||
@@ -297,11 +357,15 @@ operator's word.
|
||||
| live: a skill registered at the mesh scope is offered as `nox-mesh:<name>` in a new session on each machine | ADR 0216 |
|
||||
| the module's test: the "instead of" table is generated from the controller's answers, ordered and cut; the guard refuses ssh to a mesh machine by name, domain, internal name, address, alias and jump, and the local work-arounds for a mesh name, naming the tool — and lets git to the forge, ssh beyond the mesh and a command merely mentioning ssh through; the override is honoured only from the session's start and only when recorded | ADR 0245 |
|
||||
| the mesh MCP server's provision resolves by co-location; a machine without the mesh MCP server refuses the module by name | ADR 0027, ADR 0152 |
|
||||
| the account the agent runs as: the facts fall back to the operator account; an agent account of its own is judged unable to become root, or raises `agent-can-become-root`; its files are written only through the helper; a session as another account is refused with no override | ADR 0266 |
|
||||
| a new session on the assigned workstation lists the mesh MCP server's five tools under `mesh` ([ADR 0195](../../02-DECISIONS/0195-the-meshs-tools-are-found-by-address-not-announced-whole.md)) and answers "which node am I" from the instruction file | the exit of the build |
|
||||
|
||||
## What this does not settle
|
||||
|
||||
- **Several operator accounts on one node** (ADR 0181 decides one).
|
||||
- **Several operator accounts on one node** (ADR 0181 decides one). ADR 0266 adds a second account of
|
||||
another kind, the one agents run as, which is no person's login.
|
||||
- **An agent account on the operator's machines.** They keep their agents as the operator, because the
|
||||
agent works in the operator's desktop and files there (§9).
|
||||
- **A worker's own licence on a machine.** Every interactive session shares the node's one agent
|
||||
directory and its licence, however many run. A worker runs from a home of its own with an agent
|
||||
directory in it, bound to its own licence through the manager (to-be 39 §5); that is for when workers
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
---
|
||||
status: located
|
||||
opened: 2026-10-08
|
||||
located-in: [mesh-catalog modules/claude-code, mesh-catalog modules/sudo, mesh-controller internal/catalogue, mesh-host internal/accounts]
|
||||
fixed-by:
|
||||
amended-design: 03-DESIGN/01-to-be/36-the-operators-agent-on-a-machine.md
|
||||
---
|
||||
|
||||
# 333. An agent on the control node can become root without a person
|
||||
|
||||
## Symptom
|
||||
|
||||
On 2026-10-08 the `sudo` module's `sudo_check` on the control node answered, for the account every coding agent
|
||||
there runs as:
|
||||
|
||||
- `passwordless: true`, decided by the module's drop-in, which grants the operator account every command as
|
||||
root without a password;
|
||||
- a second grant of the same, set by hand in the machine's main sudoers file before the module existed;
|
||||
- the groups `docker` and the account's own. Membership of `docker` is root without sudo: a container started
|
||||
with the machine's root filesystem mounted.
|
||||
|
||||
So every agent session on the control node can become root without a person. It runs as the same account as
|
||||
the tool runner, so it can also read the runner's bus credential, which carries the grants of every module the
|
||||
runner serves.
|
||||
|
||||
## Why it matters
|
||||
|
||||
ADR 0259 (hq pull request 211, not yet merged)
|
||||
§8, revised after the security review of 2026-10-08 (hq pull request 211), lets the router issue an approve
|
||||
warrant, and honours a channel's `verified-sender`, only on a machine where the self-check finds that no account
|
||||
an agent runs as can become root without a person. The router and the first channel are placed on the control
|
||||
node. There the condition fails, so an agent could publish a warrant, or answer for the operator, with the
|
||||
runner's credential or as root.
|
||||
|
||||
## What the design let pass
|
||||
|
||||
- **Nothing measured it.** The `sudo` module reports what the operator account may do. No check asks which
|
||||
account an agent runs as, or whether that account can become root.
|
||||
- **One account did three jobs**: the person's login, the tool runner, and every agent. ADR 0181 decided one
|
||||
account per node and left several open.
|
||||
|
||||
## Where it is located
|
||||
|
||||
| part | owner |
|
||||
|---|---|
|
||||
| which account the agent runs as, and where its files go | mesh-catalog `modules/claude-code` |
|
||||
| the operator account's rule | mesh-catalog `modules/sudo` |
|
||||
| a node naming a second account, and the self-check's invariant | mesh-controller `internal/inventory`, `internal/catalogue`, `cmd/mesh-controller` |
|
||||
| judging whether an account can become root | the node-engine, `mesh-host` `internal/accounts` |
|
||||
|
||||
## The decision
|
||||
|
||||
[ADR 0266](../../02-DECISIONS/0266-on-the-control-node-agents-run-under-an-account-of-their-own-that-cannot-become-root-without-a-person.md):
|
||||
on the control node, agents run under an account of their own. The node-engine judges on every look that the
|
||||
account cannot become root without a person, and the self-check says it urgently when it can. The operator's
|
||||
account keeps its sudo.
|
||||
Reference in New Issue
Block a user