Critical security fix (hq issue 339). Do not merge without review. Independent of mesh-host's PR for the same issue; either may merge first.
The hole: any caller of mesh-controller.settings (or mesh-controller.command "settings set …") could set a module's places to /etc with an owner of its own, and the node-engine would chown it as root at the next push; or set accesses to / and have it mounted into a container.
Terminal only.settingsCommand refuses any layer change that adds, changes or removes places or accesses, and a clear of a layer holding either, in every process a verb runs (runVerb sets MESH_VERB for every verb; the terminal has none). A change of other keys that keeps both as they were is still taken.
The generic command verb refuses settings set / settings clear lines outright (argvFor), pointing at the settings verb.
Never at the machine's own trees, from anywhere (the terminal too):Places and AccessPlaces clean the path and refuse at/below /etc, /usr, /boot, /root, /run, /proc, /sys, /dev, /bin, /sbin, /lib*, /var/lib/mesh, /var/lib/mesh-host, and exactly /, /var, /var/lib, /home, /srv, /mnt, /opt, /tmp, … .
A setting is one line:JudgeSettings (where a layer is kept and where it is composed) refuses \n, \r, NUL in any string or key at any depth. PEM blocks alone may hold lines (strict: base64 lines of 64, last padded), because step-ca serves its root to the route proxy as a setting. The co-located test's fake PEM fixture was 46 characters on its last line (not a whole number of base64 groups) and is now 48.
Tests that fail on main (run there first):
cmd/mesh-controller/terminal_settings_test.goTestPlacesAndAccessesAreRefusedThroughEveryVerb: through argvFor + this binary's run() dispatch with MESH_VERB set as runVerb does — settings verb (one machine, whole mesh, move, drop with replace, clear) and command lines (three spellings, plus clear) are refused and the layer is unchanged; the terminal takes the placement; system paths refused at the terminal; line breaks refused. On main: settings verb, one machine: <nil>.
internal/catalogue/terminal_settings_test.go: system paths for places and accesses, one-line rule, PEM shape. On main: every system path taken, every line break taken.
Live check before writing it: the 15 layers that set places/accesses today (all on the home server and the anchor's backup) pass the new refusal; details in the issue.
merge-check.sh passes locally against a throwaway postgres (no C compiler is missing here: race-checked).
hq: issue 339 (PR to follow).
**Critical security fix (hq issue 339).** Do not merge without review. Independent of mesh-host's PR for the same issue; either may merge first.
The hole: any caller of `mesh-controller.settings` (or `mesh-controller.command "settings set …"`) could set a module's `places` to `/etc` with an owner of its own, and the node-engine would chown it as root at the next push; or set `accesses` to `/` and have it mounted into a container.
- **Terminal only.** `settingsCommand` refuses any layer change that adds, changes or removes `places` or `accesses`, and a clear of a layer holding either, in every process a verb runs (`runVerb` sets `MESH_VERB` for every verb; the terminal has none). A change of other keys that keeps both as they were is still taken.
- **The generic `command` verb** refuses `settings set` / `settings clear` lines outright (`argvFor`), pointing at the settings verb.
- **Never at the machine's own trees, from anywhere (the terminal too):** `Places` and `AccessPlaces` clean the path and refuse at/below `/etc`, `/usr`, `/boot`, `/root`, `/run`, `/proc`, `/sys`, `/dev`, `/bin`, `/sbin`, `/lib*`, `/var/lib/mesh`, `/var/lib/mesh-host`, and exactly `/`, `/var`, `/var/lib`, `/home`, `/srv`, `/mnt`, `/opt`, `/tmp`, … .
- **A setting is one line:** `JudgeSettings` (where a layer is kept and where it is composed) refuses `\n`, `\r`, NUL in any string or key at any depth. PEM blocks alone may hold lines (strict: base64 lines of 64, last padded), because step-ca serves its root to the route proxy as a setting. The co-located test's fake PEM fixture was 46 characters on its last line (not a whole number of base64 groups) and is now 48.
**Tests that fail on main** (run there first):
- `cmd/mesh-controller/terminal_settings_test.go` `TestPlacesAndAccessesAreRefusedThroughEveryVerb`: through `argvFor` + this binary's `run()` dispatch with `MESH_VERB` set as `runVerb` does — settings verb (one machine, whole mesh, move, drop with replace, clear) and `command` lines (three spellings, plus clear) are refused and the layer is unchanged; the terminal takes the placement; system paths refused at the terminal; line breaks refused. On main: `settings verb, one machine: <nil>`.
- `internal/catalogue/terminal_settings_test.go`: system paths for places and accesses, one-line rule, PEM shape. On main: every system path taken, every line break taken.
Live check before writing it: the 15 layers that set `places`/`accesses` today (all on the home server and the anchor's backup) pass the new refusal; details in the issue.
`merge-check.sh` passes locally against a throwaway postgres (no C compiler is missing here: race-checked).
hq: issue 339 (PR to follow).
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
Through the settings verb, or a settings line run by the generic command
verb, any caller of the mesh's console could place a module's directory at
/etc with an owner of its own and have the node-engine, as root, hand it
over at the next push, or mount any of the machine's paths into a container
(hq issue 339). A change to either key is now refused in every process a
verb runs, the generic verb refuses settings writes outright, and neither key
may name the machine's own trees from anywhere, the terminal included. A
line break, carriage return or NUL in any setting, which a file it is
written into reads as a line of the caller's own, is refused where a layer
is kept and where it is composed; PEM blocks alone may hold lines.
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
route-proxy provides route: its consumers are sent again after it
route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Machines
mesh-controller on novox: passed — healthy 3 times over 3m39s
mesh-controller on the rest running it: sent
build-agent on ace: passed — healthy 3 times over 3m31s
build-agent on the rest running it: sent
route-proxy on ace: passed — healthy 3 times over 3m41s
route-proxy on the rest running it: sent
Transitions
2026-10-08 22:00 (new) → proposed (announced): novox/mesh-controller#168's head announced
2026-10-08 22:06 proposed → checked (checked): the verdict names this commit
2026-10-08 22:06 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
2026-10-08 22:21 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
2026-10-08 22:21 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path
2026-10-08 22:35 delivering → delivered (done): its walk is done: every machine of its deploy plan passed, or was left as its policy says
The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.
<!-- mesh-delivery:view -->
**Delivery** `novox/mesh-controller@f5824b31c696` — **delivered** since 2026-10-08T22:35:42Z
**Delivery plan** — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- tier 2: route-proxy
- ace: receives build-agent, route-proxy
- g14: receives build-agent
- novox: receives mesh-controller, build-agent, route-proxy
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- route-proxy provides route: its consumers are sent again after it
- route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
**Machines**
- mesh-controller on novox: passed — healthy 3 times over 3m39s
- mesh-controller on the rest running it: sent
- build-agent on ace: passed — healthy 3 times over 3m31s
- build-agent on the rest running it: sent
- route-proxy on ace: passed — healthy 3 times over 3m41s
- route-proxy on the rest running it: sent
**Transitions**
- 2026-10-08 22:00 (new) → proposed (announced): novox/mesh-controller#168's head announced
- 2026-10-08 22:06 proposed → checked (checked): the verdict names this commit
- 2026-10-08 22:06 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
- 2026-10-08 22:21 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
- 2026-10-08 22:21 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path
- 2026-10-08 22:35 delivering → delivered (done): its walk is done: every machine of its deploy plan passed, or was left as its policy says
The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
build-agent keeps data (ADR 0233): what it holds is backed up before it moves
route-proxy provides route: its consumers are sent again after it
route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as build-1791496832099882991 (builds --log build-1791496832099882991).
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-08T21:34:01Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-08T21:34:01Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.049s
ok github.com/novox/mesh-controller/cmd/mesh-controller 211.458s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.018s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.022s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.020s
ok github.com/novox/mesh-controller/examples/route-proxy 1.126s
ok github.com/novox/mesh-controller/internal/artifacts 1.053s
ok github.com/novox/mesh-controller/internal/broker 2.855s
ok github.com/novox/mesh-controller/internal/builder 2.663s
ok github.com/novox/mesh-controller/internal/catalogue 5.207s
ok github.com/novox/mesh-controller/internal/conditions 2.415s
ok github.com/novox/mesh-controller/internal/envfile 1.028s
ok github.com/novox/mesh-controller/internal/facts 1.027s
ok github.com/novox/mesh-controller/internal/identity 5.013s
ok github.com/novox/mesh-controller/internal/inventory 91.845s
ok github.com/novox/mesh-controller/internal/lease 17.671s
ok github.com/novox/mesh-controller/internal/licences 4.543s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.015s
ok github.com/novox/mesh-controller/internal/link 54.476s
ok github.com/novox/mesh-controller/internal/lint 2.176s
ok github.com/novox/mesh-controller/internal/outward 1.042s
ok github.com/novox/mesh-controller/internal/overlay 1.020s
ok github.com/novox/mesh-controller/internal/secrets 1.510s
ok github.com/novox/mesh-controller/internal/store 3.411s
ok github.com/novox/mesh-controller/internal/testbus 1.186s
ok github.com/novox/mesh-controller/internal/token 1.020s
**Merge check** at `f5824b31`
- `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)
- `mesh/repo-check`: **PASS** — its merge-check.sh passed
**Delivery plan** — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
- tier 0: mesh-controller
- tier 1: build-agent
- tier 2: route-proxy
- ace: receives build-agent, route-proxy
- g14: receives build-agent
- novox: receives mesh-controller, build-agent, route-proxy
- shanks: receives build-agent
- build-agent keeps data (ADR 0233): what it holds is backed up before it moves
- route-proxy provides route: its consumers are sent again after it
- route-proxy keeps data (ADR 0233): what it holds is backed up before it moves
Run by the build seat on novox as `build-1791496832099882991` (`builds --log build-1791496832099882991`).
```
--- the gate: build-agent, mesh-controller, route-proxy
mesh-controller: ok, 52 tool(s); WARNING: controller stay(s) up and say(s) not how it is ready — judged by liveness alone, refused from 2026-11-18 (ADR 0240 rule 8)
long-running resources without health: 1
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-08T21:34:01Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-08T21:34:01Z
machines:
- a machine (f35): composes
- holds mesh-dns-resolver (idt): composes
- holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
- a machine (yvbpsm): composes
a merge would rebuild 3 module(s) in 3 tier(s): build-agent, mesh-controller, route-proxy
--- its merge-check.sh (go toolchain)
ok github.com/novox/mesh-controller/cmd/mesh-builder 1.049s
ok github.com/novox/mesh-controller/cmd/mesh-controller 211.458s
ok github.com/novox/mesh-controller/examples/objectstore-provisioner 1.018s
ok github.com/novox/mesh-controller/examples/postgres-provisioner 1.022s
ok github.com/novox/mesh-controller/examples/redis-provisioner 1.020s
ok github.com/novox/mesh-controller/examples/route-proxy 1.126s
ok github.com/novox/mesh-controller/internal/artifacts 1.053s
ok github.com/novox/mesh-controller/internal/broker 2.855s
ok github.com/novox/mesh-controller/internal/builder 2.663s
ok github.com/novox/mesh-controller/internal/catalogue 5.207s
ok github.com/novox/mesh-controller/internal/conditions 2.415s
ok github.com/novox/mesh-controller/internal/envfile 1.028s
ok github.com/novox/mesh-controller/internal/facts 1.027s
ok github.com/novox/mesh-controller/internal/identity 5.013s
ok github.com/novox/mesh-controller/internal/inventory 91.845s
ok github.com/novox/mesh-controller/internal/lease 17.671s
ok github.com/novox/mesh-controller/internal/licences 4.543s
ok github.com/novox/mesh-controller/internal/licences/adapters 1.015s
ok github.com/novox/mesh-controller/internal/link 54.476s
ok github.com/novox/mesh-controller/internal/lint 2.176s
ok github.com/novox/mesh-controller/internal/outward 1.042s
ok github.com/novox/mesh-controller/internal/overlay 1.020s
ok github.com/novox/mesh-controller/internal/secrets 1.510s
ok github.com/novox/mesh-controller/internal/store 3.411s
ok github.com/novox/mesh-controller/internal/testbus 1.186s
ok github.com/novox/mesh-controller/internal/token 1.020s
```
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Critical security fix (hq issue 339). Do not merge without review. Independent of mesh-host's PR for the same issue; either may merge first.
The hole: any caller of
mesh-controller.settings(ormesh-controller.command "settings set …") could set a module'splacesto/etcwith an owner of its own, and the node-engine would chown it as root at the next push; or setaccessesto/and have it mounted into a container.settingsCommandrefuses any layer change that adds, changes or removesplacesoraccesses, and a clear of a layer holding either, in every process a verb runs (runVerbsetsMESH_VERBfor every verb; the terminal has none). A change of other keys that keeps both as they were is still taken.commandverb refusessettings set/settings clearlines outright (argvFor), pointing at the settings verb.PlacesandAccessPlacesclean the path and refuse at/below/etc,/usr,/boot,/root,/run,/proc,/sys,/dev,/bin,/sbin,/lib*,/var/lib/mesh,/var/lib/mesh-host, and exactly/,/var,/var/lib,/home,/srv,/mnt,/opt,/tmp, … .JudgeSettings(where a layer is kept and where it is composed) refuses\n,\r, NUL in any string or key at any depth. PEM blocks alone may hold lines (strict: base64 lines of 64, last padded), because step-ca serves its root to the route proxy as a setting. The co-located test's fake PEM fixture was 46 characters on its last line (not a whole number of base64 groups) and is now 48.Tests that fail on main (run there first):
cmd/mesh-controller/terminal_settings_test.goTestPlacesAndAccessesAreRefusedThroughEveryVerb: throughargvFor+ this binary'srun()dispatch withMESH_VERBset asrunVerbdoes — settings verb (one machine, whole mesh, move, drop with replace, clear) andcommandlines (three spellings, plus clear) are refused and the layer is unchanged; the terminal takes the placement; system paths refused at the terminal; line breaks refused. On main:settings verb, one machine: <nil>.internal/catalogue/terminal_settings_test.go: system paths for places and accesses, one-line rule, PEM shape. On main: every system path taken, every line break taken.Live check before writing it: the 15 layers that set
places/accessestoday (all on the home server and the anchor's backup) pass the new refusal; details in the issue.merge-check.shpasses locally against a throwaway postgres (no C compiler is missing here: race-checked).hq: issue 339 (PR to follow).
Delivery
novox/mesh-controller@f5824b31c696— delivered since 2026-10-08T22:35:42ZDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Machines
Transitions
The commit's note under
refs/notes/mesh-plankeeps every transition:git log --notes=mesh-plan.Merge check at
f5824b31mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on novox as
build-1791496832099882991(builds --log build-1791496832099882991).