From 82c54870edf9a8e7adc1cec4ed7b5c424da4b5c7 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 28 Oct 2026 09:58:27 +0100 Subject: [PATCH] =?UTF-8?q?A=20module's=20trusted=20list=20of=20callers=20?= =?UTF-8?q?in=20the=20SDK,=20so=20the=20vault's=20secret-ask=20answers=20t?= =?UTF-8?q?he=20callers=20its=20manifest=20names=20as=20the=20runtime=20pr?= =?UTF-8?q?oved=20them=20(issue=20774,=20ADR=200563=20=C2=A78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go/caller_test.go | 15 +++++++++++++++ go/callers.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 go/callers.go diff --git a/go/caller_test.go b/go/caller_test.go index 7fae75c..8f87cb1 100644 --- a/go/caller_test.go +++ b/go/caller_test.go @@ -75,6 +75,21 @@ func TestACallerInTheArgumentsIsNeverTheCaller(t *testing.T) { if got != "shanks.node-tools" { t.Fatalf("with a caller in the arguments the tool read %q, want the runtime's", got) } + // A module's trusted list of callers (issue 774, ADR 0563 §8) grants the callers it names as the runtime proved them: + // a module on any machine, . exactly, person. exactly; no wildcard, never one unproven. + callers, err := ParseCallers([]byte(`{"callers":["node-tools","ace.claude-code","person.jo","*"]}`)) + if err != nil { + t.Fatal(err) + } + for caller, want := range map[string]bool{"shanks.node-tools": true, "ace.claude-code": true, "g14.claude-code": false, + "person.jo": true, "person.node-tools": false, Unproven: false, "": false, "novox.mesh-vault": false} { + if callers.Allows(caller) != want { + t.Errorf("%q allowed: %v, want %v", caller, !want, want) + } + } + if _, err := ParseCallers([]byte(`{"callers":["x"],"more":1}`)); err == nil { + t.Error("a list with a field it does not know was read") + } } // A runtime that names no caller — one from before issue 365, or a call on the subject that names none — is diff --git a/go/callers.go b/go/callers.go new file mode 100644 index 0000000..74a7ce3 --- /dev/null +++ b/go/callers.go @@ -0,0 +1,48 @@ +package stdio + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +// Callers is a module's trusted list of who may call a verb of its (novox/hq ADR 0459 §2, ADR 0563 §8), as the mesh +// writes a list setting into a file the module reads: `{"callers": [...]}`. An entry is a module's name (that module on +// any machine), exactly ., or exactly person.. Each caller is the runtime's proof (CallerOf), never +// an argument; nobody beyond the list is granted, a wildcard is no entry, and an unproven caller never matches. +type Callers []string + +// ParseCallers reads a callers file: the list under `callers`, and nothing else. +func ParseCallers(raw []byte) (Callers, error) { + var f struct { + Callers Callers `json:"callers"` + } + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&f); err != nil { + return nil, fmt.Errorf("the callers setting is not a list of callers: %v", err) + } + return f.Callers, nil +} + +// Allows says whether caller, as the runtime proved it, is one the list names. +func (c Callers) Allows(caller string) bool { + if caller == "" || caller == Unproven { + return false + } + for _, entry := range c { + if entry == "" || strings.ContainsAny(entry, "*> ") { + continue + } + if entry == caller { + return true + } + if !strings.Contains(entry, ".") { + if _, module, ok := strings.Cut(caller, "."); ok && module == entry && !strings.HasPrefix(caller, "person.") { + return true + } + } + } + return false +}