Issue 340: record the operator's decisions, and open 342 for passwords taken as settings
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:
+15
-6
@@ -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
|
||||
|
||||
+29
-7
@@ -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.
|
||||
Reference in New Issue
Block a user