Files
mesh-catalog/modules/mesh-vault/cmd/mesh-vault/sensitive_test.go
T
jschoubben f83e6e4713 Make each sensitive vault verb judge its own caller
The controller judged the operator's account for approving or deleting a
retirement, rotating a secret and exporting sealed copies; it is not
placed, and these verbs are reached through the mesh MCP server on each
machine. Each now answers the operator's account (person.<name>), or a
caller naming its own recorded hand act for that verb and subject, and
nothing else (ADR 0590 §8, issue 1126).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 12:29:56 +01:00

73 lines
4.0 KiB
Go

package main
import (
"strings"
"testing"
"time"
)
// ADR 0590 §8 (issue 1126): approving or deleting a retirement, rotating a secret and exporting sealed copies each
// answer the operator's account, or a caller naming the hand act it recorded for that verb and that subject, and
// nothing else: the controller that judged for them is not placed.
func TestEachSensitiveVerbJudgesItsOwnCaller(t *testing.T) {
now := time.Date(2026, 11, 2, 12, 0, 0, 0, time.UTC)
acts := map[string]HandActSeen{
"mine": {ID: "mine", By: "novox.mesh-healers", At: now.Add(-time.Hour), Verb: "mesh-vault.rotate", Args: []string{"own/g14/app/key"}},
"old": {ID: "old", By: "novox.mesh-healers", At: now.Add(-25 * time.Hour), Verb: "rotate", Args: []string{"own/g14/app/key"}},
"other": {ID: "other", By: "novox.mesh-config", At: now.Add(-time.Hour), Verb: "rotate", Args: []string{"own/g14/app/key"}},
"del": {ID: "del", By: "novox.mesh-healers", At: now.Add(-time.Hour), Verb: "provisioner_delete", Args: []string{"d-app"}},
}
read := func(id string) (*HandActSeen, error) {
if a, ok := acts[id]; ok {
return &a, nil
}
return nil, nil
}
clock := func() time.Time { return now }
for _, row := range []struct {
name, caller, verb, act string
about []string
want string // empty: allowed
}{
{"the operator's account", "person.jochen", SensitiveRotate, "", []string{"own/g14/app/key"}, ""},
{"the operator's account exports", "person.jochen", SensitiveExport, "", nil, ""},
{"unproven", "unproven", SensitiveVaultExport, "", nil, "proved none"},
{"a bare person prefix", "person.", SensitiveExport, "", nil, "named none"},
{"a module with no act", "novox.mesh-healers", SensitiveRotate, "", []string{"own/g14/app/key"}, "named none"},
{"a module's own act", "novox.mesh-healers", SensitiveRotate, "mine", []string{"own/g14/app/key"}, ""},
{"an act of another subject", "novox.mesh-healers", SensitiveRotate, "mine", []string{"own/ace/app/key"}, "not own/ace/app/key"},
{"an act of another verb", "novox.mesh-healers", SensitiveRotate, "del", []string{"own/g14/app/key"}, "warrants no rotate"},
{"an act of another caller", "novox.mesh-healers", SensitiveRotate, "other", []string{"own/g14/app/key"}, "recorded by novox.mesh-config"},
{"an act too old", "novox.mesh-healers", SensitiveRotate, "old", []string{"own/g14/app/key"}, "older than"},
{"an act not recorded", "novox.mesh-healers", SensitiveDelete, "none", []string{"d-app"}, "holds no hand act"},
{"a delete's own act", "novox.mesh-healers", SensitiveDelete, "del", []string{"d-app"}, ""},
} {
err := JudgeSensitive(read, clock, row.caller, row.verb, row.act, row.about...)
if (row.want == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), row.want)) {
t.Errorf("%s: %v, want %q", row.name, err, row.want)
}
}
}
// The tools themselves judge: a module's call with no act is refused before anything is read or changed, and the
// operator's account goes through.
func TestTheSensitiveToolsRefuseACallerTheyDoNotJudgeTheirs(t *testing.T) {
env := &Env{Root: t.TempDir()}
defs := definitions(env)
for _, name := range []string{Seat + ".rotate", "provisioner_retire_approve", "provisioner_delete", "secret_export",
"secret_vault_export"} {
if _, err := toolNamed(t, defs, name).runAs("novox.some-module", map[string]any{"why": "x", "consumer": "c",
"confirm": "c", "consumers": []any{"c"}, "node": "g14", "module": "app", "name": "key"}); err == nil ||
!strings.Contains(err.Error(), "ADR 0590") {
t.Errorf("%s answered a module with no act: %v", name, err)
}
}
if _, err := toolNamed(t, defs, "secret_export").runAs("person.jochen", map[string]any{}); err != nil {
t.Errorf("the operator's export was refused: %v", err)
}
// Listing holders alone is no sealed copy: any proven caller reads it.
if _, err := toolNamed(t, defs, "secret_export").runAs("novox.some-module", map[string]any{"sealed": false}); err != nil {
t.Errorf("a listing without sealed copies was refused: %v", err)
}
}