Issue 340: the whole layer of a module with a mergeable file is the terminal's, and the text still registrable is a risk said
The review found that a mergeable file takes any key, not only those it names, and that the text a session reads stays registrable through a verb; say both, and the follow-ups the module took.
This commit is contained in:
+17
-4
@@ -34,8 +34,10 @@ Two more routes write the same files:
|
||||
- **The controller's settings verb.** The module's `managed_settings` and `mcp_servers` settings land in a
|
||||
mergeable file. [Issue 339](../339-a-verb-can-place-a-directory-at-the-machines-own/00-report.md) made a
|
||||
setting the terminal's alone when a trusted file asks for it with a placeholder. A mergeable file takes
|
||||
its keys without one, so `mesh-controller.settings {module: claude-code, values: {"managed_settings":
|
||||
{"hooks": …}}}` was taken from any caller.
|
||||
any key without one, so `mesh-controller.settings {module: claude-code, values: {"managed_settings":
|
||||
{"hooks": …}}}` was taken from any caller. The review found the same for every module with a mergeable
|
||||
file: an identity provider's empty runtime configuration took a `url` of the caller's, and its provisioner
|
||||
would send its admin login there.
|
||||
- **Items whose text runs something.** A skill or a slash command may hold shell lines that the agent runs
|
||||
when it loads the item, before any permission is asked. A skill's front matter may hold hooks and tools it
|
||||
pre-approves. A subagent placed in a home may start a tool server or change the permission mode from its
|
||||
@@ -64,6 +66,13 @@ 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
|
||||
|
||||
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.
|
||||
|
||||
## Ordering
|
||||
|
||||
**This must land before ADR 0266's agent account is switched on** (hq pull request 225). Until it lands, the
|
||||
@@ -81,7 +90,9 @@ at the controller's terminal. An agent's attempt through any mesh tool is refuse
|
||||
runtime speaks to them and calls each one: a hook, settings with a hook, a status line or a permission
|
||||
mode, a permission rule added or removed, a tool server by command or by address, and a skill, command or
|
||||
subagent with a shell line or with `hooks`, `allowed-tools`, `mcpServers` or `permissionMode` in its front
|
||||
matter, at the mesh, node and home scopes. Each is refused with an error that names this issue, and
|
||||
matter, at the mesh, node and home scopes. Front matter hidden behind a byte-order mark, written as a flow
|
||||
mapping or with merged keys, and front matter that does not parse, are refused, and so is an `@` reference
|
||||
to a file outside the item. Each is refused with an error that names this issue, and
|
||||
nothing is put on the bus or placed in a home. On the commit before the fix every one of them was taken.
|
||||
- **In the module's render tests.** A hook, settings or a skill with a shell line already on the bus, or
|
||||
kept in the module's own file by an older build, is never rendered. The tool server and the instruction
|
||||
@@ -90,7 +101,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. The layer is
|
||||
the mesh, and through the `command` verb, is refused with an error naming this issue. 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
|
||||
the fix.
|
||||
- **Live, after both are taken:** the calls in the diagnosis's last section are each refused, and
|
||||
|
||||
+52
-29
@@ -38,21 +38,36 @@ documentation of skills, subagents, plugins, output styles and memory, read 2026
|
||||
| output style, instructions, rule file | the plugin, the managed instruction file, a home | text the model reads; no field runs anything | still registered |
|
||||
| a script beside a skill | the plugin, or a home | only when the agent runs it, through the session's own rules and the guard | still registered |
|
||||
|
||||
**Text is a lower class, and stays registrable.** Instructions, output styles and skills steer the agent, as
|
||||
any text in its context does. A session still asks before it runs what the text suggests, under the
|
||||
operator's permission rules and the mesh's guard. A skill is also invoked by the model on its own, which is
|
||||
why its shell lines and pre-approved tools are refused. What the text alone asks for is judged by the
|
||||
session. Blocking all registered text would end the shared instructions the mesh relies on, and would not
|
||||
stop text reaching an agent by other routes (a file in a repository, a tool's answer). This is a judgement,
|
||||
and it is said: a session in a mode that runs everything without asking would run what a registered text
|
||||
asks for. That mode is the operator's own choice for their own session.
|
||||
**Text is a lower class, and stays registrable through a verb. This is a risk, and it is the operator's to
|
||||
take or close.** At the mesh and node scopes, an instruction section, an output style, and a skill, command or
|
||||
subagent without the fields above are still registered through the module's tools, so any caller of the mesh
|
||||
MCP server can register them, an agent confined on the control node included. They reach every session on the
|
||||
nodes they name, as text the model reads:
|
||||
|
||||
- the instruction sections land in the managed instruction file, which every session reads first and treats
|
||||
as the mesh's own word;
|
||||
- a skill is invoked by the model on its own when its description matches what it is doing.
|
||||
|
||||
So a registered text can ask the operator's agent, on every node, to run a command. Nothing in it runs by
|
||||
itself: the session still asks before it runs what the text suggests, under the operator's permission rules and
|
||||
the mesh's guard. But a session in a mode that runs without asking, or a person who approves what looks like the
|
||||
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.
|
||||
|
||||
### The routes that write the same files
|
||||
|
||||
- **The module's tools**: refused (below).
|
||||
- **The controller's settings verb**: `TerminalKeys` (`internal/catalogue/terminal_keys.go`) counted the
|
||||
settings a file asks for by placeholder alone. A mergeable file asks for every key it holds without one.
|
||||
The module's settings file is mergeable, so `managed_settings`, `mcp_servers` and `role` were the verb's.
|
||||
settings a file asks for by placeholder alone. A mergeable file takes every key a layer sets, named in its
|
||||
content or not. The module's settings file is mergeable, so `managed_settings`, `mcp_servers` and `role` were
|
||||
the verb's. The review found the same for every other mergeable file: 13 runtime configurations start empty,
|
||||
and a provider reads what lands in them. A `url` set through a verb on the identity provider's would have
|
||||
had its provisioner send the admin login to the caller's listener.
|
||||
- **The catalogue's defaults**: the module's settings file holds empty values only. No layer sets them in
|
||||
the catalogue.
|
||||
- **The bus**: the module's `config` and `servers` state. Only a holder of the module's grants writes them,
|
||||
@@ -65,22 +80,22 @@ asks for. That mode is the operator's own choice for their own session.
|
||||
|
||||
**In the controller** (pull request in `mesh-controller`):
|
||||
|
||||
1. A mergeable file asks for every key its own content names. Unless the file says `"trusted": false`, each
|
||||
is a terminal key, refused through every verb like the keys issue 339 named. For the agent's module,
|
||||
`managed_settings`, `mcp_servers` and `role` are the terminal's.
|
||||
2. The refusal names this issue beside 339.
|
||||
1. A module with a mergeable file not marked `"trusted": false` has its **whole layer** set at the terminal.
|
||||
Through any verb, any change to it is refused, whatever the key, a key no file names included. Reading it
|
||||
through a verb still answers. For the agent's module that covers `managed_settings`, `mcp_servers` and
|
||||
`role`.
|
||||
2. A mergeable file's own keys are also counted as keys it asks for, so `module check` names the file when it
|
||||
does not say whether it is trusted.
|
||||
3. The refusal names this issue beside 339.
|
||||
|
||||
This reaches every mergeable file whose content names keys, not only the agent's. On 2026-10-09 that was 12
|
||||
more files in the catalogue: the database web app, the agent's licence manager, the dashboards, the agent
|
||||
memory store, the chat client's look, the watcher's and the messenger's chat ids, the flow editor, the route
|
||||
adapter, the search engine, the database pooler and the network controller. In the media catalogue it was 5:
|
||||
the library sorter and four download clients' settings. Through a verb these keys were the caller's before.
|
||||
Many decide what a consumer trusts or reaches: a route, a chat id that verifies a sender, a password.
|
||||
`module check`'s count of files that do not say whether they are trusted rose from 9 to 22 over the catalogue. An author opts a
|
||||
harmless one out with `"trusted": false`. That waits until the node-engine accepts the field, as issue 339
|
||||
says.
|
||||
|
||||
A key the file does not name still lands through a verb. Nothing reading the agent's file reads such a key.
|
||||
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
|
||||
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
|
||||
counted, but is refused all the same.
|
||||
|
||||
**In the module** (pull request in `mesh-catalog`):
|
||||
|
||||
@@ -92,9 +107,15 @@ A key the file does not name still lands through a verb. Nothing reading the age
|
||||
operator's `managed_settings` under the mesh's own keys.
|
||||
3. A skill, subagent or command holding a shell line, or `hooks`, `mcpServers`, `permissionMode` or
|
||||
`allowed-tools` in its front matter, is refused at every scope, and the import tool refuses one too.
|
||||
4. What the module kept in its own file before is judged again when it renders.
|
||||
5. Removing stays: unregistering a tool server, a hook or settings takes away what runs.
|
||||
6. A new read-only tool, `claude_code_registrations`, lists every registration with its `by`, `at`, and
|
||||
Front matter is read as YAML, after a byte-order mark, in flow style and with merged keys. Front matter
|
||||
that does not close, does not parse, holds a key twice or is not a mapping is refused rather than guessed
|
||||
at.
|
||||
4. A skill or command that refers with `@` to a file that is not one of its own files is refused. The agent
|
||||
reads such a file into its prompt when the item loads, so the item would carry another file's words, or a
|
||||
secret, into every session it reaches.
|
||||
5. What the module kept in its own file before is judged again when it renders.
|
||||
6. Removing stays: unregistering a tool server, a hook or settings takes away what runs.
|
||||
7. A new read-only tool, `claude_code_registrations`, lists every registration with its `by`, `at`, and
|
||||
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`.
|
||||
@@ -130,7 +151,9 @@ Each of these must be refused, saying why, and change nothing:
|
||||
- `<node>/claude-code.claude_code_settings_set {settings: {"model": "x"}}`, and with `scope: node, nodes: all`;
|
||||
- `<node>/claude-code.claude_code_permission_add {list: allow, rule: Bash}`;
|
||||
- `<node>/claude-code.claude_code_mcp_register {name: x, url: "https://example.org/mcp", nodes: all}`;
|
||||
- `<node>/claude-code.claude_code_skill_register {name: x, content: "---\nname: x\nallowed-tools: Bash\n---\nx"}`;
|
||||
- `<node>/claude-code.claude_code_skill_register {name: x, content: "---\nname: x\nallowed-tools: Bash\n---\nx"}`, and
|
||||
with the front matter as a flow mapping, and with `@~/.ssh/id_ed25519` in its body;
|
||||
- `mesh-controller.settings {module: keycloak, node: <its node>, values: "{\"url\": \"http://192.0.2.1\"}"}`;
|
||||
- `mesh-controller.settings {module: claude-code, values: "{\"managed_settings\": {\"model\": \"x\"}}"}`, and
|
||||
with `node`, and `clear: "true"`;
|
||||
- `mesh-controller.command {command: "settings set claude-code '{\"mcp_servers\":{}}' --node <a node>"}`.
|
||||
|
||||
Reference in New Issue
Block a user