claude-code: refuse registering what a session runs, so no agent can plant it in the operator's sessions
mesh/merge-gate pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

The tool runner is not told who called, so a hook, a managed setting, a
permission rule or a tool server registered through these tools could come
from an agent confined on another node and run as the operator there
(hq issue 340). They are set at the controller's terminal now; a skill,
subagent or command that runs something by itself is refused too, kept
hooks and settings are never rendered, and claude_code_registrations
lists what is registered and by which node.
This commit is contained in:
jochen
2026-10-09 02:30:11 +02:00
parent a1406e7d79
commit 736743fedc
11 changed files with 650 additions and 336 deletions
@@ -67,44 +67,26 @@ func TestEachKindLandsInItsOnePlace(t *testing.T) {
register(Item{Kind: KindAgent, Name: "reviewer", Scope: ScopeMesh, Files: one("reviewer", "---\nname: reviewer\n---\nx")})
register(Item{Kind: KindCommand, Name: "ship", Scope: ScopeMesh, Files: one("ship", "ship it")})
register(Item{Kind: KindOutputStyle, Name: "terse", Scope: ScopeMesh, Files: one("terse", "---\nname: terse\n---\nshort")})
register(Item{Kind: KindHook, Name: "guard", Scope: ScopeMesh, Event: "PreToolUse", Matcher: "Bash",
Command: "${HOOK_DIR}/guard.sh", Files: map[string]string{"guard.sh": "#!/bin/sh\nexit 0\n"}})
register(Item{Kind: KindInstructions, Name: "conventions", Scope: ScopeMesh, Files: one("conventions", "Commit in the imperative.")})
register(Item{Kind: KindSettings, Name: SettingsName, Scope: ScopeMesh,
Settings: map[string]any{"permissions": map[string]any{"deny": []any{"Bash(rm -rf:*)"}}}})
plugin := pluginOf(t, w)
root := "plugins/" + Plugin + "/"
for _, want := range []string{".claude-plugin/marketplace.json", root + ".claude-plugin/plugin.json",
root + "skills/review/SKILL.md", root + "skills/review/scripts/run.sh", root + "agents/reviewer.md",
root + "commands/ship.md", root + "output-styles/terse.md", root + "hooks/hooks.json", root + "hooks/guard/guard.sh"} {
root + "commands/ship.md", root + "output-styles/terse.md"} {
if _, ok := plugin[want]; !ok {
t.Errorf("the plugin lacks %s", want)
}
}
if !plugin[root+"skills/review/scripts/run.sh"].Executable || !plugin[root+"hooks/guard/guard.sh"].Executable {
if !plugin[root+"skills/review/scripts/run.sh"].Executable {
t.Error("a script is not executable")
}
var hooks struct {
Hooks map[string][]struct {
Matcher string `json:"matcher"`
Hooks []struct {
Command string `json:"command"`
} `json:"hooks"`
} `json:"hooks"`
}
_ = json.Unmarshal([]byte(plugin[root+"hooks/hooks.json"].Content), &hooks)
// The mesh's own guard on the shell first (novox/hq ADR 0245), then the hook registered.
if pre := hooks.Hooks["PreToolUse"]; len(pre) != 2 || pre[0].Matcher != GuardMatcher || pre[0].Hooks[0].Command != GuardCommand() ||
pre[1].Matcher != "Bash" || pre[1].Hooks[0].Command != `"${CLAUDE_PLUGIN_ROOT}/hooks/guard"/guard.sh` {
t.Errorf("the hook's command does not name its directory, quoted: %s", plugin[root+"hooks/hooks.json"].Content)
}
if !strings.Contains(w["CLAUDE.md"], "### conventions\n\nCommit in the imperative.") {
t.Errorf("the instruction section is not in the managed instruction file:\n%s", w["CLAUDE.md"])
}
var managed map[string]any
_ = json.Unmarshal([]byte(w["managed-settings.json"]), &managed)
if managed["permissions"] == nil || managed["enabledPlugins"].(map[string]any)[Plugin+"@"+Plugin] != true {
if managed["enabledPlugins"].(map[string]any)[Plugin+"@"+Plugin] != true {
t.Errorf("managed settings: %v", managed)
}
if _, inPlugin := plugin[root+"settings.json"]; inPlugin {
@@ -174,25 +156,6 @@ func TestTheMeshsOwnKeysCannotBeSetOrReplaced(t *testing.T) {
}
}
func TestSettingsLayMeshThenNodeAndJoinTheirLists(t *testing.T) {
c := Config{
Mesh: []Item{{Kind: KindSettings, Scope: ScopeMesh, Settings: map[string]any{
"permissions": map[string]any{"deny": []any{"A"}}, "model": "mesh-model"}}},
Node: []Item{{Kind: KindSettings, Scope: ScopeNode, Settings: map[string]any{
"permissions": map[string]any{"deny": []any{"A", "B"}}, "model": "node-model"}}},
}
out := Render(Facts{Console: "x"}, Settings{ManagedSettings: map[string]any{"permissions": map[string]any{"allow": []any{"C"}}}},
nil, "/h", nil, c)
var managed struct {
Permissions map[string][]string `json:"permissions"`
Model string `json:"model"`
}
_ = json.Unmarshal([]byte(out["managed-settings.json"]), &managed)
if strings.Join(managed.Permissions["deny"], ",") != "A,B" || strings.Join(managed.Permissions["allow"], ",") != "C" || managed.Model != "node-model" {
t.Fatalf("%+v", managed)
}
}
func TestTheHomeScopeOwnsOnlyWhatItPlaced(t *testing.T) {
p, w := node(t, "laptop")
state, view := memConfig{}, NewConfigView(p)
@@ -248,7 +211,7 @@ func TestAnItemAboveTheLimitOrMisshapenIsRefused(t *testing.T) {
"a path outside": {Kind: KindSkill, Name: "s", Scope: ScopeMesh, Files: map[string]string{"SKILL.md": "x", "../escape": "x"}},
"a hook at home": {Kind: KindHook, Name: "h", Scope: ScopeHome, Event: "Stop", Command: "true"},
"settings at home": {Kind: KindSettings, Name: SettingsName, Scope: ScopeHome, Settings: map[string]any{"model": "x"}},
"an unknown event": {Kind: KindHook, Name: "h", Scope: ScopeMesh, Event: "Whenever", Command: "true"},
"a hook anywhere": {Kind: KindHook, Name: "h", Scope: ScopeMesh, Event: "Stop", Command: "true"},
}
for label, it := range cases {
if it.Problem() == "" {