Issue 340: record the operator's decisions, and open 342 for passwords taken as settings
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

Shared agent text is the terminal's until a surface of the operator's own
updates it, the wider controller rule stands, and a password is not a
setting: 342 names every module that takes one through a setting.
This commit is contained in:
jochen
2026-10-09 09:29:39 +02:00
parent 1dbfb2d85a
commit 8a6f19e457
3 changed files with 101 additions and 13 deletions
@@ -66,12 +66,19 @@ own settings file. It does not see a tool call that changes the managed files of
- no node's layer of the module's settings set `managed_settings` or `mcp_servers`, and no layer had ever
been replaced.
## What stays open
## The operator's decisions (2026-10-09)
Text a session reads (instruction sections, output styles, and skills, commands and subagents that run
nothing themselves) is still registered through a verb at the mesh and node scopes. It can ask the operator's
agent on every node to run something, which the session then asks about. The diagnosis says what that risk is.
The operator is deciding whether it becomes terminal-only too.
1. **The wider controller rule stands.** Every module with a mergeable file that does not say
`"trusted": false` takes its settings at the controller's terminal alone.
2. **Shared agent text is the terminal's, for now.** Instruction sections, output styles, skills, commands and
subagents for every node or some are set at the controller's terminal, in the agent module's settings, and
refused through the tools. A person's own home stays free. In the operator's words: "once we build a UI, we
can also update the context through it". A surface of the operator's own, which can tell the operator from
an agent, is the intended way back.
3. **Passwords are not settings.** They come from the key vault and can be rotated. Every module found taking
a password, token or API key through a setting is recorded in
[issue 342](../342-a-password-is-taken-as-a-setting-and-outranks-the-vaults/00-report.md), to move to
vault-held secrets, not to stay terminal-only settings.
## Ordering
@@ -101,7 +108,9 @@ at the controller's terminal. An agent's attempt through any mesh tool is refuse
settings file holds.
- **In the controller's tests, through the real verb dispatch.** Setting, changing, dropping or clearing
the module's `managed_settings`, `mcp_servers` or `role` through the `settings` verb, for one machine or
the mesh, and through the `command` verb, is refused with an error naming this issue. So is a new `url` on a
the mesh, and through the `command` verb, is refused with an error naming this issue. Text for every node or
some, of each kind, is refused through the tools and the import tool, and lands when set in the module's
settings at the terminal; the home scope is still registered. So is a new `url` on a
module whose mergeable runtime configuration starts empty, while a file marked `"trusted": false` stays the
verb's. The layer is
unchanged after every refusal. At the terminal the same is taken. These tests fail on the commit before
@@ -54,10 +54,14 @@ the mesh's guard. But a session in a mode that runs without asking, or a person
mesh's own instruction, would run it as the operator. That is the same sideways path as a hook, made one
approval longer.
What closing it costs: the mesh's shared instructions and skills, which agents keep up today, would be
registered at the controller's terminal alone. It also does not stop text reaching an agent by other routes
(a file in a repository, a tool's answer). On 2026-10-09 the operator is deciding whether this text becomes
terminal-only too. This issue records the answer when it is given.
What closing it costs: the mesh's shared instructions and skills, which agents keep up today, are set at the
controller's terminal alone. It also does not stop text reaching an agent by other routes (a file in a
repository, a tool's answer).
**The operator decided on 2026-10-09: this text is the terminal's, for now.** At the mesh and node scopes every
kind is refused through the tools and set at the controller's terminal. The home scope, one account's own
directory, stays registrable. In the operator's words: "once we build a UI, we can also update the context
through it". A surface of the operator's own, which knows who is using it, is the intended way back.
### The routes that write the same files
@@ -91,7 +95,9 @@ terminal-only too. This issue records the answer when it is given.
This reaches every module with a mergeable file, not only the agent's. On 2026-10-09 that was 25 modules in the
catalogue and 11 in the media catalogue, every one with a file that says nothing about trust. Through a verb
their settings were any caller's before. Many decide what a consumer trusts or reaches: a provider's address, a
route, a chat id that verifies a sender, a password. From now on the operator sets them at the terminal. An
route, a chat id that verifies a sender. Some take a password, token or API key; the operator ruled on
2026-10-09 that a password is not a setting, and those modules are
[issue 342](../342-a-password-is-taken-as-a-setting-and-outranks-the-vaults/00-report.md). From now on the operator sets them at the terminal. An
author gives a verb back a file nothing trusts with `"trusted": false`. That waits until the node-engine accepts
the field, as issue 339 says. `module check`'s count of files that do not say whether they are trusted rose from
9 to 22 over the catalogue. It counts the files whose content names keys. A file that starts empty is not
@@ -119,13 +125,29 @@ counted, but is refused all the same.
whether it is still rendered and why not, beside what the terminal set for the node. `by` is the node
whose tool runner served the call. The caller was never recorded, because the runner is not told. A tool
server's registration kept no `by`.
8. **Shared text is set at the terminal** (the operator's decision, above). Through the tools, an instruction
section, output style, skill, command or subagent at the mesh or node scope is refused, saying where it is set,
and the import tool is refused. The module's settings gain `instructions`, `output_styles`, `skills`,
`commands` and `agents`. A setting holds no line break (issue 339), so each text is a list of its lines.
They land where a registration did, laid over what was registered before. What the operator writes there is
not judged for what it asks of a session, because it is the operator's word. An item that is not well formed
is left out, and the render says so. Text registered before is still rendered, and may be unregistered.
9. **Tool servers from the terminal alone** (a separate pull request, to be merged only after the laptop's tool
server is set in that node's `mcp_servers` at the terminal). The managed tool-server file then holds the
mesh's own entry and the `mcp_servers` setting alone. A server left on the bus is listed and may be
unregistered, and no longer starts in a session. That closes, for tool servers, the route by which a holder of
a tool runner's credential could write the bus directly. Text registered before still renders from the bus.
The same move for it is the step left: the three sections set at the terminal, then text no longer rendered
from the bus.
### Existing registrations
Read on 2026-10-09 through the module's list tools and the settings verb, on all four nodes:
- three instruction sections for the mesh, registered through the laptop: still rendered;
- one tool server for the laptop alone, a program in the operator's own home: still rendered;
- three instruction sections for the mesh, registered through the laptop: still rendered, until they are set at
the terminal;
- one tool server for the laptop alone, a program in the operator's own home: still rendered, until item 9
merges, which waits for the operator to set it in the laptop's `mcp_servers` at the terminal;
- no hook, no settings, no permission rule;
- no layer of the module's settings sets `managed_settings` or `mcp_servers` on any node, and none was ever
replaced.
@@ -0,0 +1,57 @@
---
status: open
opened: 2026-10-09
located-in: []
fixed-by:
amended-design:
---
# 342. A password is taken as a setting, and outranks the vault's
## Symptom
Reading the catalogue for [issue 340](../340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md)
on 2026-10-09 found modules that take a password, a token or an API key through a **setting**. Nothing was run
against a node, and no node's layers were read for such values, so that no secret is copied into a session.
The operator's rule, said on 2026-10-09: **a password is not a setting.** It comes from the key vault, and it can
be rotated there. A setting is neither: it is kept in the controller's layers, shown by the settings verb to
whoever reads them, kept in the layer history, and rotated by nobody.
Two shapes, found by reading the manifests and the modules' clients:
1. **A mergeable file that holds the vault's secret, which a setting overrides.** The file's content names the
secret as `${secret:…}`, and a layer setting the same key replaces it in the file:
- `baserow` (`password`), `grafana` (`password`), `letta` (`password`), `unifi` (`password`);
- `searxng` (`server.secret_key`);
- the media library's sorter, `kometa` (`plex.token`, `tmdb.apikey`).
2. **A client that reads a secret from its settings file before the vault's.** The settings file starts empty, and
the client reads `cfg.<secret> ?? <the vault's file or environment>`, so a setting wins over the vault:
- `gitea` (`token`), `home-assistant` (`token`), `icecast` (`password`), `influxdb` (`token`),
`mailu` (`apiKey`), `nextcloud` (`password`), `nodered` (`token`);
- in the media catalogue: `bazarr`, `jackett`, `ombi` and `tautulli` (`apiKey`), `nzbget` and `qbittorrent`
(`password`).
Not affected, as read: `jira`, `confluence` and `gitlab` read their token from their own secret's file alone. The
watcher and the messenger take a token *file* path, not a token. The four media download steps read the API key
from the application's own configuration, and the download clients' credentials from the provisions.
## Why it is an issue
- A secret in a setting cannot be rotated by the vault: the setting outranks the rotated value until someone
removes it by hand.
- It is read back by the settings verb and kept in the layers' history.
- Until issue 340's controller fix, any caller of the settings verb could set one, and so point a module at a
credential of the caller's choice. Since that fix these keys are the terminal's. That keeps agents out, and it
still leaves the secret a setting.
## Acceptance test (the operator's view)
No module takes a password, token or API key from a setting. Each comes from a vault-held secret that rotates,
and a setting naming one is refused with the reason.
## How it will be checked
To be written with the fix: per module, a test that a setting naming the secret's key is refused (or ignored, and
said), and the catalogue's check refusing a mergeable file whose own keys hold `${secret:…}` that a layer could
replace.