diff --git a/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md b/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md index 5bcc36bf..8ea5ff78 100644 --- a/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md +++ b/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/00-report.md @@ -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 diff --git a/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/01-diagnosis.md b/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/01-diagnosis.md index 6d8403d1..e44ae0b8 100644 --- a/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/01-diagnosis.md +++ b/04-ISSUES/340-an-agent-can-plant-a-hook-in-every-agent-session-on-the-mesh/01-diagnosis.md @@ -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. diff --git a/04-ISSUES/342-a-password-is-taken-as-a-setting-and-outranks-the-vaults/00-report.md b/04-ISSUES/342-a-password-is-taken-as-a-setting-and-outranks-the-vaults/00-report.md new file mode 100644 index 00000000..d8df5551 --- /dev/null +++ b/04-ISSUES/342-a-password-is-taken-as-a-setting-and-outranks-the-vaults/00-report.md @@ -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. ?? `, 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.