From e2a45b18ba036caa437b444f4974a12d8dea0639 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 9 Oct 2026 12:03:28 +0200 Subject: [PATCH] =?UTF-8?q?Refuse=20a=20module=20of=20its=20own=20account?= =?UTF-8?q?=20running=20as=20the=20node's=20operator=20or=20agent=20accoun?= =?UTF-8?q?t=20(hq=20ADR=200259=20=C2=A78,=20review=20L4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/catalogue/runtime.go | 10 ++++++++++ internal/catalogue/runtime_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/internal/catalogue/runtime.go b/internal/catalogue/runtime.go index d0e3bc32..b7f3fbe4 100644 --- a/internal/catalogue/runtime.go +++ b/internal/catalogue/runtime.go @@ -266,6 +266,16 @@ func (r Resolution) ownRuntimes(with Rendering) ([]map[string]any, error) { program := runtime.Bundles[0] var out []map[string]any for _, m := range own { + // Never the node's operator account, nor the account agents run as there (the review of 2026-10-09): + // either would hand what it holds back to the very accounts it is kept from. + switch { + case r.Account != "" && m.RunsAs == r.Account: + return nil, fmt.Errorf("%s runs as %s, the operator's account on %s: a module of its own account never "+ + "runs as it (novox/hq ADR 0259 §8)", m.Module, m.RunsAs, r.Node) + case r.AgentAccount != "" && m.RunsAs == r.AgentAccount: + return nil, fmt.Errorf("%s runs as %s, the account agents run as on %s: a module of its own account "+ + "never runs as it (novox/hq ADR 0259 §8)", m.Module, m.RunsAs, r.Node) + } credential, declared := m.OwnSecrets["broker"] if !declared { return nil, fmt.Errorf("%s runs as its own account and declares no own secret broker", m.Module) diff --git a/internal/catalogue/runtime_test.go b/internal/catalogue/runtime_test.go index cba8b954..9c16bdb5 100644 --- a/internal/catalogue/runtime_test.go +++ b/internal/catalogue/runtime_test.go @@ -503,3 +503,29 @@ func TestAModuleOfItsOwnAccountIsServedByARuntimeOfItsOwn(t *testing.T) { t.Error("a module of its own account composed without a runtime program") } } + +// The review of 2026-10-09 (L4): a module of its own account never runs as the node's operator account, nor +// as the account agents run as there — either would hand what it holds back to the accounts it is kept from. +func TestAModuleOfItsOwnAccountIsRefusedTheOperatorsAndTheAgentsAccount(t *testing.T) { + with := Rendering{ArtifactStore: "anchor.internal:5101", + Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}, "telegram": {"broker": "own"}}} + goRuntime := Manifest{Module: RuntimeModule, Version: "1", + OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}}, + Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "go", + System: "arch", From: "cmd/node-tools"}}}} + goRuntime, err := goRuntime.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, + Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) + if err != nil { + t.Fatal(err) + } + for _, account := range []string{"ops", "agent"} { + telegram := aToolsModule(t, "telegram", "tools/index.js") + telegram.RunsAs, telegram.SecretsOwner = account, account + telegram.OwnSecrets = OwnSecrets{"broker": {Path: "/var/lib/telegram/broker"}} + _, err := Resolution{Node: "anchor", Account: "ops", AgentAccount: "agent", + Modules: []Manifest{telegram, goRuntime}}.ownRuntimes(with) + if err == nil || !strings.Contains(err.Error(), account) { + t.Errorf("telegram running as %s was composed: %v", account, err) + } + } +}