Privilege no caller named controller in the vault; match a hand act's verb exactly

The controller is not placed (ADR 0590), yet the vault still answered a
caller named controller alone (accept), took its relay of who asked
(secret-ask, rotate's by) and sealed it the core's credentials. Those
branches and accept go with their tests; a node's account alone is never
minted or rotated here. The sensitive-verb judge now needs the act's verb to
be the vault's own, mesh-vault.<verb>, not any verb ending so (issue 1126
review 1).

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-02 12:29:56 +01:00
parent 3ed3266dde
commit ecc7621274
16 changed files with 88 additions and 399 deletions
+3 -7
View File
@@ -136,7 +136,7 @@ the declaration sealed by the vault to their machine, which the controller carri
machine's sealing key as its own node-engine stated it in its own name, in `move-in`'s first step (`sealing_key`).
A machine whose engine stated none, any other key, any other module and any other name are refused and recorded.
## Taking values in: `accept`, `secret-ask`, `issued`, `rotate`
## Taking values in: `secret-ask`, `issued`, `rotate`
Design 57's V2 (issue 675; ADR 0349 clauses 8 to 10, ADR 0277 §22–§27 and ADR 0305 with the vault in the
controller's place). `intake.go`.
@@ -156,12 +156,8 @@ controller still asks them itself for a module filled the old way, until R1 (iss
answer both until then. Everything the vault keeps is its account's: its directories, the export it keeps, the
grant files the controller still writes for it.
- `accept` — two steps: node, module, name (a module's own secret) or provider, provision (a provision's one
credential), and `by`; then `call` and `sealed`. The controller's terminal seals the value it read to the call's
key before it leaves the terminal. Kept with origin `accepted`; an accepted provision credential is never remade
(`rotate` refuses it); a value a provider's software issued is not taken by `accept`.
- `secret-ask` — one step: node, module, name, desk and `asked_by` (who asked the controller, as the bus named its
caller). The ask is recorded in `asks.jsonl` before anything else, and refused while another ask for the same
- `secret-ask` — one step: node, module, name and desk, asked by a caller the vault's manifest names (`accept`, and
the controller's relay of who asked it, went with the controller, ADR 0590). The ask is recorded in `asks.jsonl` before anything else, and refused while another ask for the same
secret is open (two minutes at most) or when three were made in the last hour. The vault then asks the desk's
prompt itself, naming who asked as the record has it, with `timeout_seconds` 20. The answer is how it ended
(`given`, dismissed, timed-out, empty, refused, failed) and who asked; how each ask ended is recorded.
-38
View File
@@ -20,7 +20,6 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"time"
)
@@ -103,40 +102,3 @@ func (v *Vault) recordNodeKey(node, key, caller string) error {
}
return nil
}
// coreRefusal says why the controller may not have name sealed to key, or "" when it may: name is a core module's
// own secret and key is its machine's sealing key as that machine's engine stated it.
func (v *Vault) coreRefusal(name, key string) string {
parts := strings.Split(name, "/")
if len(parts) != 4 || parts[0] != "own" {
return fmt.Sprintf("the controller is handed a core module's own secret alone, own/<node>/<%s>/<name>, sealed "+
"to its machine (ADR 0349 §16); %s is not one", strings.Join(CoreModules, "|"), name)
}
node, module := parts[1], parts[2]
if !slices.Contains(CoreModules, module) {
return fmt.Sprintf("%s is not one of the core's modules (%s): its secrets reach its machine through the "+
"provisioner or its declaration, never through the controller's ask (ADR 0349 §16)", module,
strings.Join(CoreModules, ", "))
}
stated, held, err := v.Store.NodeKey(node)
if err != nil {
return fmt.Sprintf("%s's sealing key cannot be read: %v", node, err)
}
if !held {
return fmt.Sprintf("%s's node-engine has stated no sealing key to the vault (it does so at the move), so "+
"there is no key of that machine's own to seal %s to", node, name)
}
want, err := publicPEM(stated)
if err != nil {
return fmt.Sprintf("%s's sealing key as recorded is not usable: %v", node, err)
}
named, err := askerKey(key)
if err != nil {
return err.Error()
}
if named != want {
return fmt.Sprintf("%s is sealed only to %s's sealing key as its node-engine stated it, and the key named "+
"is another", name, node)
}
return ""
}
@@ -20,7 +20,6 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/seal"
"github.com/nats-io/nats-server/v2/server"
"github.com/nats-io/nats.go"
)
@@ -205,74 +204,3 @@ authorization {
type errString string
func (e errString) Error() string { return string(e) }
func TestOnARealBusTheControllerGetsACoreCredentialSealedOnlyToItsNodesKey(t *testing.T) {
ask, anchorPub, anchorPriv, heard := coreOnABus(t)
got, err := ask("own/anchor/mesh-provisioner-client/provisioner-broker", pemOf(t, anchorPub))
if err != nil {
t.Fatal(err)
}
if got.Name != "own/anchor/mesh-provisioner-client/provisioner-broker" || got.Generation != 1 {
t.Errorf("answered %+v", got)
}
opened, err := seal.Open(got.Sealed, anchorPriv)
if err != nil || opened != brokerValue {
t.Fatalf("anchor's key opens %q, %v", opened, err)
}
// The controller's own key, or any key but anchor's, is refused: it carries the box and never opens it.
mine, err := seal.GenerateKeyPair()
if err != nil {
t.Fatal(err)
}
if got, err := ask("own/anchor/mesh-provisioner-client/provisioner-broker", mine.PublicKey); err == nil {
t.Errorf("the controller was handed anchor's core credential sealed to its own key: %+v", got)
} else if !strings.Contains(err.Error(), "sealing key") {
t.Errorf("refused for another reason: %v", err)
}
// A module that is not the core's is refused, though anchor's key is the one named.
if got, err := ask("own/anchor/postgres/superuser-password", pemOf(t, anchorPub)); err == nil {
t.Errorf("the controller was handed a leaf module's secret: %+v", got)
} else if !strings.Contains(err.Error(), "core") {
t.Errorf("refused for another reason: %v", err)
}
// A machine whose engine stated no key: nothing to seal to, so nothing is handed.
laptopPub, _ := aSealingKey(t)
if _, err := ask("own/laptop/nats/broker", pemOf(t, laptopPub)); err == nil {
t.Error("the controller was handed a core credential for a machine whose engine stated no key")
}
all := heard()
if !strings.Contains(all, "mesh.seat.mesh-vault.call.get.controller") {
t.Fatalf("the listener heard nothing of the asks:\n%s", all)
}
for _, v := range []string{brokerValue, movedValue} {
for _, form := range []string{v, base64.StdEncoding.EncodeToString([]byte(v))} {
if strings.Contains(all, form) {
t.Fatal("a value crossed the bus in the clear")
}
}
}
}
// Only the controller is answered by this rule: a machine's client asking for a core credential, which no plan
// names, is still refused by the running rule, and a node-engine is not an asker.
func TestTheCoreRuleAnswersTheControllerAlone(t *testing.T) {
v := newVault(t, Declared{Read: assignments{}.read})
pub, _ := aSealingKey(t)
if _, err := moveIn(t, v, "node.anchor", clientsBroker(pub), brokerValue); err != nil {
t.Fatal(err)
}
for _, caller := range []string{"provisioner.anchor", "node.anchor", "anchor.mesh-provisioner-client"} {
if _, err := v.Get(caller, "own/anchor/mesh-provisioner-client/provisioner-broker", pemOf(t, pub)); err == nil {
t.Errorf("%s was handed a core credential", caller)
}
}
// The controller is answered for nothing else: not a pair credential, not a provision's.
for _, name := range []string{"pair/anchor/postgres/anchor/gitea", "provision/anchor/postgres"} {
if _, err := v.Get(ControllerCaller, name, pemOf(t, pub)); err == nil {
t.Errorf("the controller was handed %s", name)
}
}
}
+6 -104
View File
@@ -86,8 +86,6 @@ func ProvisionSecret(provider, provision string) (string, error) {
// callKey is one call's key and what the call takes in. Never written anywhere.
type callKey struct {
verb, secret, by string
// origin is what an `accept` call keeps the value as: accepted, or issued for a covered account (issue 787).
origin string
// ask is the ask's record for `secret-ask`, which the call's id is.
ask *AskRecord
// move is what `move-in` takes: the row of the controller's store (move.go).
@@ -107,19 +105,6 @@ type Opened struct {
AskedBy string `json:"asked_by,omitempty"`
}
// fromTheController refuses every caller but the controller: only it asks the vault to take a value in, and only
// it carries a value given at its terminal.
func fromTheController(caller, verb string) error {
if caller != ControllerCaller {
if caller == "" {
caller = stdio.Unproven
}
return fmt.Errorf("%s takes a value from the controller alone, which carries it sealed to this vault's key "+
"for one call; %s is not the controller", verb, caller)
}
return nil
}
// fromAGrantedCaller refuses a caller the runtime did not prove: `rotate` and `issued` answer every caller whose
// manifest invokes them, which the bus grants it (novox/hq ADR 0570, Consequences), no longer the controller alone.
func fromAGrantedCaller(caller, verb string) error {
@@ -258,80 +243,6 @@ func NamedAccount(secret string) bool {
return len(parts) == 4 && parts[0] == "own" && (parts[3] == "broker" || parts[3] == "provisioner-broker")
}
// AcceptOpen opens a call to take a value given at the controller's terminal for secret, which the terminal
// seals to the call's key before it leaves it (ADR 0349 clause 8: the vault alone opens an accepted value).
//
// **With origin issued it is a covered account the controller minted** (ADR 0374, issue 787): the controller alone
// hands it, for a covered secret alone, and a re-issue replaces the value in place. Without origin it is a value
// given at the terminal, as before.
func (v *Vault) AcceptOpen(caller, secret, by, origin string) (Opened, error) {
if err := fromTheController(caller, "accept"); err != nil {
return Opened{}, err
}
if err := validName(secret); err != nil {
return Opened{}, err
}
kept := OriginAccepted
switch origin {
case "":
if rec, held, err := v.Store.Read(secret); err != nil {
return Opened{}, err
} else if held && rec.Origin == OriginIssued && !Covered(secret) {
return Opened{}, fmt.Errorf("%s is issued by its provider's software, and `secret issued` is the only way "+
"it enters the vault (ADR 0305 §10); nothing is taken", secret)
}
case OriginIssued:
if !Covered(secret) {
return Opened{}, fmt.Errorf("%s is not a bus account the controller mints for a core or moved module "+
"(%s), so it is not handed with origin issued; nothing is taken", secret, strings.Join(CoveredSecrets, ", "))
}
kept = OriginIssued
default:
return Opened{}, fmt.Errorf("the origin %q is not one accept takes (issued, or none for a value given at the "+
"terminal); nothing is taken", origin)
}
id, key, err := v.openCall("accept", secret, by, nil)
if err != nil {
return Opened{}, err
}
v.callsMu.Lock()
if c := v.pending[id]; c != nil {
c.origin = kept
}
v.callsMu.Unlock()
return Opened{Call: id, SealTo: key, Secret: secret}, nil
}
// AcceptTake opens the value sealed to the call's key and keeps it, accepted: never remade by the vault.
func (v *Vault) AcceptTake(caller, call, sealed string) (Changed, error) {
if err := fromTheController(caller, "accept"); err != nil {
return Changed{}, err
}
c, err := v.takeCall("accept", call)
if err != nil {
return Changed{}, err
}
value, err := c.open(sealed)
if err != nil {
return Changed{}, err
}
defer zero(value)
if strings.TrimSpace(string(value)) == "" {
return Changed{}, errors.New("the value given is empty; nothing was taken")
}
origin := c.origin
if origin == "" {
origin = OriginAccepted
}
if origin == OriginIssued {
// **The vault's mint wins** (issue 774): during the delivery a controller older than the hand-over may still mint
// a covered account and hand it here; it never replaces an account the vault minted, judged under the store's
// lock, so the value and the hash the bus loaded stay the vault's.
return v.keepWith(c.secret, string(value), someoneOr(c.by, caller), caller, origin, keepOptions{refuse: v.vaultMinted})
}
return v.keep(c.secret, string(value), someoneOr(c.by, caller), caller, origin)
}
// vaultMinted refuses a value handed for a secret holding an account the vault minted itself (mint.go): one is never
// replaced but by the vault's own rotation.
func (v *Vault) vaultMinted(before Record) string {
@@ -351,26 +262,19 @@ func (v *Vault) vaultMinted(before Record) string {
// AskOf is what an ask is for, and where.
type AskOf struct {
Node, Module, Name, Desk string
// AskedBy is who asked the controller, as the bus named its caller; the controller's word, taken from the
// controller alone. A caller asking directly is named by the runtime's proof, never by this (ADR 0563 §8).
AskedBy string
}
// askedByFor is who asked a secret-ask as the bus says it (novox/hq ADR 0563 §8): for a call relayed by the controller,
// the caller the bus named to the controller, which it takes from the controller alone, as before; for a caller the
// manifest names, that caller as the runtime proved it. Any other caller is refused: no caller names itself.
// askedByFor is who asked a secret-ask as the bus says it (novox/hq ADR 0563 §8): a caller the manifest names, as the
// runtime proved it. Any other caller is refused: no caller names itself, and none relays another's word.
func (v *Vault) askedByFor(caller string, a AskOf) (string, error) {
switch {
case caller == ControllerCaller:
return askedByName(a.AskedBy), nil
case v.AskCallers.Allows(caller):
if v.AskCallers.Allows(caller) {
return askedByName(caller), nil
}
if caller == "" {
caller = stdio.Unproven
}
return "", fmt.Errorf("secret-ask answers the controller and the callers the vault's manifest names, and %s is "+
"neither; nobody was asked to type anything", caller)
return "", fmt.Errorf("secret-ask answers the callers the vault's manifest names, and %s is not one; nobody was "+
"asked to type anything", caller)
}
// AskOpen records an ask for a module's own secret before any prompt opens, holding its bounds — one open ask
@@ -787,9 +691,7 @@ func (v *Vault) RotateFor(caller, secret string, one Narrow, by, why string) (Ch
if err := fromAGrantedCaller(caller, "rotate"); err != nil {
return Changed{}, err
}
if caller != ControllerCaller {
by = "" // only the controller relays who asked at its terminal; any other caller is who asked
}
by = "" // who asked is the caller the runtime proved: no caller relays another's word (novox/hq ADR 0590)
if strings.TrimSpace(why) == "" {
return Changed{}, errors.New("not rotated: a rotation says why it was asked (`why`)")
}
@@ -75,7 +75,7 @@ func everything(t *testing.T, v testVault, more ...any) string {
const typedAtTheDesk = "123456789:AAEhBP0av28P4XFQnIuR-o-7Xnz1kkUzW3g"
func telegramAsk(by string) AskOf {
return AskOf{Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop", AskedBy: by}
return AskOf{Node: "anchor", Module: "telegram", Name: "telegram-token", Desk: "laptop"}
}
// ---- the vault's secret-ask test (ADR 0349 "How it is checked") ---------------------------------------------
@@ -84,33 +84,34 @@ func telegramAsk(by string) AskOf {
// it; the typed value opens only in the vault; the controller carries ciphertext only.
func TestAnAskIsRecordedBeforeThePromptAndTheTypedValueOpensOnlyInTheVault(t *testing.T) {
v := newVault(t, AllowList{"anchor.node-provisioner": {"own/anchor/telegram/telegram-token"}})
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14/claude-code, via the console"))
v.AskCallers = stdio.Callers{"g14.claude-code"}
opened, err := v.AskOpen("g14.claude-code", telegramAsk("g14/claude-code, via the console"))
if err != nil {
t.Fatal(err)
}
// Recorded before any prompt opens: the record holds the ask now, with no end.
asks, _ := v.Store.Asks(10)
if len(asks) != 1 || asks[0].Event != AskOpened || asks[0].ID != opened.Call || asks[0].AskedBy != "g14/claude-code" ||
asks[0].Caller != ControllerCaller || asks[0].Desk != "laptop" || asks[0].Secret != "own/anchor/telegram/telegram-token" {
if len(asks) != 1 || asks[0].Event != AskOpened || asks[0].ID != opened.Call || asks[0].AskedBy != "g14.claude-code" ||
asks[0].Caller != "g14.claude-code" || asks[0].Desk != "laptop" || asks[0].Secret != "own/anchor/telegram/telegram-token" {
t.Fatalf("the record before the prompt: %+v", asks)
}
// The prompt names the asker as the bus named it, cut to a name: what the vault answers is what it recorded.
if opened.AskedBy != "g14/claude-code" {
if opened.AskedBy != "g14.claude-code" {
t.Errorf("the prompt would name %q", opened.AskedBy)
}
// The desk seals what is typed to the call's key; the controller passes it on.
changed, err := v.AskEnd(ControllerCaller, opened.Call, sealToCall(t, opened.SealTo, typedAtTheDesk+"\n"), "")
changed, err := v.AskEnd("g14.claude-code", opened.Call, sealToCall(t, opened.SealTo, typedAtTheDesk+"\n"), "")
if err != nil {
t.Fatal(err)
}
if changed.Generation != 1 || changed.By != "g14/claude-code" {
if changed.Generation != 1 || changed.By != "g14.claude-code" {
t.Errorf("kept as %+v", changed)
}
if got := handedTo(t, v, "anchor.node-provisioner", "own/anchor/telegram/telegram-token"); got != typedAtTheDesk {
t.Errorf("the vault holds %q, not what was typed with its line ending taken off", got)
}
rec, _, _ := v.Store.Read("own/anchor/telegram/telegram-token")
if rec.Origin != OriginTyped || rec.ChangedBy != "g14/claude-code" || rec.ChangedVia != ControllerCaller {
if rec.Origin != OriginTyped || rec.ChangedBy != "g14.claude-code" || rec.ChangedVia != "" {
t.Errorf("the record says origin %q, by %q, via %q", rec.Origin, rec.ChangedBy, rec.ChangedVia)
}
asks, _ = v.Store.Asks(10)
@@ -125,33 +126,34 @@ func TestAnAskIsRecordedBeforeThePromptAndTheTypedValueOpensOnlyInTheVault(t *te
// One open ask per secret, and three an hour (ADR 0277 §24), read from the vault's own record.
func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
v := newVault(t, nil)
first, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
v.AskCallers = stdio.Callers{"g14.claude-code"}
first, err := v.AskOpen("g14.claude-code", telegramAsk("g14.claude-code"))
if err != nil {
t.Fatal(err)
}
if _, err := v.AskOpen(ControllerCaller, telegramAsk("ace.claude-code")); err == nil ||
if _, err := v.AskOpen("g14.claude-code", telegramAsk("ace.claude-code")); err == nil ||
!strings.Contains(err.Error(), "still open (asked by g14.claude-code)") {
t.Fatalf("a second ask while one is open: %v", err)
}
// Another secret is not held back by it.
other := telegramAsk("ace.claude-code")
other.Name = "webhook-secret"
if _, err := v.AskOpen(ControllerCaller, other); err != nil {
if _, err := v.AskOpen("g14.claude-code", other); err != nil {
t.Fatalf("another secret's ask was held back: %v", err)
}
if _, err := v.AskEnd(ControllerCaller, first.Call, "", "dismissed"); err != nil {
if _, err := v.AskEnd("g14.claude-code", first.Call, "", "dismissed"); err != nil {
t.Fatal(err)
}
for i := 0; i < 2; i++ {
o, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
o, err := v.AskOpen("g14.claude-code", telegramAsk("g14.claude-code"))
if err != nil {
t.Fatalf("ask %d of the hour: %v", i+2, err)
}
if _, err := v.AskEnd(ControllerCaller, o.Call, "", "timed-out"); err != nil {
if _, err := v.AskEnd("g14.claude-code", o.Call, "", "timed-out"); err != nil {
t.Fatal(err)
}
}
if _, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err == nil ||
if _, err := v.AskOpen("g14.claude-code", telegramAsk("g14.claude-code")); err == nil ||
!strings.Contains(err.Error(), "3 times in the last hour") {
t.Fatalf("a fourth ask in the hour: %v", err)
}
@@ -165,13 +167,14 @@ func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
}
// An ask left open counts as open for SecretAskOpenFor only: a prompt that died does not hold the secret.
w := newVault(t, nil)
w.AskCallers = stdio.Callers{"g14.claude-code"}
clock := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
w.Now = func() time.Time { return clock }
if _, err := w.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err != nil {
if _, err := w.AskOpen("g14.claude-code", telegramAsk("g14.claude-code")); err != nil {
t.Fatal(err)
}
clock = clock.Add(SecretAskOpenFor + time.Second)
if _, err := w.AskOpen(ControllerCaller, telegramAsk("g14.claude-code")); err != nil {
if _, err := w.AskOpen("g14.claude-code", telegramAsk("g14.claude-code")); err != nil {
t.Errorf("an ask left open past its time still held the secret: %v", err)
}
// 774 (ADR 0563 §8): a caller the manifest names asks directly, is named by its proven caller and never by the
@@ -186,7 +189,7 @@ func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
if err != nil || opened.AskedBy != "g14.node-tools" {
t.Fatalf("a named caller's ask: %+v, %v", opened, err)
}
if _, err := x.AskEnd(ControllerCaller, opened.Call, "", "dismissed"); err == nil {
if _, err := x.AskEnd("g14.claude-code", opened.Call, "", "dismissed"); err == nil {
t.Error("another caller ended a named caller's ask")
}
if _, err := x.AskOpen("ace.node-tools", telegramAsk("")); err == nil || !strings.Contains(err.Error(), "still open (asked by g14.node-tools)") {
@@ -199,10 +202,11 @@ func TestAnAskIsBoundedToOneOpenPerSecretAndThreeAnHour(t *testing.T) {
})
}
// Only the controller reaches a desk's prompt, so only it opens and ends an ask; a value not sealed to the
// call's key, or a call that waited too long, keeps nothing.
func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T) {
// Only a caller the vault's manifest names opens an ask, and only it ends it; a value not sealed to the call's key, or
// a call that waited too long, keeps nothing.
func TestAnAskIsTakenFromANamedCallerAloneAndOnlySealedToItsCall(t *testing.T) {
v := newVault(t, nil)
v.AskCallers = stdio.Callers{"g14.claude-code"}
for _, caller := range []string{"g14.node-tools", "unproven", "", "person.jochen"} {
if _, err := v.AskOpen(caller, telegramAsk("x")); err == nil {
t.Errorf("%q opened an ask", caller)
@@ -211,7 +215,7 @@ func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T)
if asks, _ := v.Store.Asks(10); len(asks) != 0 {
t.Fatalf("a refused caller filled the record: %+v", asks)
}
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
opened, err := v.AskOpen("g14.claude-code", telegramAsk("g14.claude-code"))
if err != nil {
t.Fatal(err)
}
@@ -219,7 +223,7 @@ func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T)
t.Error("another caller ended the ask")
}
other, _, _ := box.GenerateKey(rand.Reader)
if _, err := v.AskEnd(ControllerCaller, opened.Call, sealToCall(t, base64.StdEncoding.EncodeToString(other[:]), "x"), ""); err == nil ||
if _, err := v.AskEnd("g14.claude-code", opened.Call, sealToCall(t, base64.StdEncoding.EncodeToString(other[:]), "x"), ""); err == nil ||
!strings.Contains(err.Error(), "not sealed to this call") {
t.Errorf("a value sealed to another key: %v", err)
}
@@ -227,7 +231,7 @@ func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T)
t.Error("a value not sealed to the call was kept")
}
// The call's key is forgotten once used: the same call cannot be answered twice.
if _, err := v.AskEnd(ControllerCaller, opened.Call, sealToCall(t, opened.SealTo, "x"), ""); err == nil {
if _, err := v.AskEnd("g14.claude-code", opened.Call, sealToCall(t, opened.SealTo, "x"), ""); err == nil {
t.Error("a call was answered twice")
}
asks, _ := v.Store.Asks(1)
@@ -237,18 +241,18 @@ func TestAnAskIsTakenFromTheControllerAloneAndOnlySealedToItsCall(t *testing.T)
// A call that waited longer than its time is closed.
clock := time.Date(2026, 10, 14, 9, 0, 0, 0, time.UTC)
v.Now = func() time.Time { return clock }
late, err := v.IssuedOpen(ControllerCaller, "plex-api", "anchor", "plex")
late, err := v.IssuedOpen("g14.claude-code", "plex-api", "anchor", "plex")
if err != nil {
t.Fatal(err)
}
clock = clock.Add(CallOpenFor + time.Second)
if _, err := v.IssuedTake(ControllerCaller, late.Call, sealToCall(t, late.SealTo, "x")); err == nil {
if _, err := v.IssuedTake("g14.claude-code", late.Call, sealToCall(t, late.SealTo, "x")); err == nil {
t.Error("a call that waited too long was taken")
}
// An empty answer gives nothing and says so.
clock = clock.Add(time.Hour)
empty, _ := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
if _, err := v.AskEnd(ControllerCaller, empty.Call, sealToCall(t, empty.SealTo, " \n"), ""); err == nil {
empty, _ := v.AskOpen("g14.claude-code", telegramAsk("g14.claude-code"))
if _, err := v.AskEnd("g14.claude-code", empty.Call, sealToCall(t, empty.SealTo, " \n"), ""); err == nil {
t.Error("an empty answer was kept")
}
if asks, _ := v.Store.Asks(1); asks[0].Outcome != "empty" {
@@ -365,62 +369,14 @@ func TestAnIssuingProviderAnswersOnlyTheVaultsCallSealedToTheVault(t *testing.T)
!strings.Contains(err.Error(), "software") {
t.Errorf("rotate of an issued value: %v", err)
}
if _, err := v.AcceptOpen(ControllerCaller, "provision/anchor/plex-api", "person.jochen", ""); err == nil {
t.Error("an issued value was taken by accept")
}
if strings.Contains(everything(t, v), "plex-token") {
t.Fatal("the issued value is in something the vault said or wrote in the clear")
}
}
// ---- accept and rotate -------------------------------------------------------------------------------------
// A value given at the controller's terminal is sealed there to the vault's call and opened here alone; an
// accepted provision's credential is never remade; a module's own given value rotates as a made one (ADR 0228).
func TestAnAcceptedValueOpensInTheVaultAloneAndIsNotRemade(t *testing.T) {
v := newVault(t, AllowList{"g14.node-provisioner": {"provision/novox/postgres", "own/g14/dunst/token"}})
if _, err := v.AcceptOpen("g14.claude-code", "provision/novox/postgres", "", ""); err == nil {
t.Error("accept was opened for a caller that is not the controller")
}
for name, value := range map[string]string{"provision/novox/postgres": "pw-of-last-year", "own/g14/dunst/token": "tok"} {
opened, err := v.AcceptOpen(ControllerCaller, name, "jochen", "")
if err != nil {
t.Fatal(err)
}
changed, err := v.AcceptTake(ControllerCaller, opened.Call, sealToCall(t, opened.SealTo, value))
if err != nil {
t.Fatal(err)
}
if changed.By != "jochen" {
t.Errorf("%s accepted by %q", name, changed.By)
}
if got := handedTo(t, v, "g14.node-provisioner", name); got != value {
t.Errorf("%s holds %q", name, got)
}
rec, _, _ := v.Store.Read(name)
if rec.Origin != OriginAccepted || rec.ChangedVia != ControllerCaller {
t.Errorf("%s: origin %q via %q", name, rec.Origin, rec.ChangedVia)
}
}
if _, err := v.Rotate(ControllerCaller, "provision/novox/postgres", "jochen", "leaked"); err == nil ||
!strings.Contains(err.Error(), "not remade") {
t.Errorf("rotate of an accepted credential: %v", err)
}
rotated, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen", "leaked")
if err != nil || rotated.Generation != 2 {
t.Fatalf("rotate of a module's own given value: %+v %v", rotated, err)
}
if got := handedTo(t, v, "g14.node-provisioner", "own/g14/dunst/token"); got == "tok" || len(got) != 40 {
t.Errorf("rotated to %q", got)
}
if strings.Contains(everything(t, v), "pw-of-last-year") {
t.Fatal("an accepted value is in something the vault said or wrote in the clear")
}
}
// Rotate: refused for a secret not held and for an unproven caller; who asked is the proven caller, or the
// person the controller names when it carries the act — both kept.
func TestRotateKeepsWhoAskedAndWhoCarriedIt(t *testing.T) {
func TestRotateKeepsTheProvenCallerAsWhoAsked(t *testing.T) {
v := newVault(t, nil)
if _, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen", "leaked"); err == nil {
t.Error("a secret not held was rotated")
@@ -431,11 +387,12 @@ func TestRotateKeepsWhoAskedAndWhoCarriedIt(t *testing.T) {
if _, err := v.Rotate("unproven", "own/g14/dunst/token", "", "leaked"); err == nil {
t.Error("an unproven caller rotated")
}
if _, err := v.Rotate(ControllerCaller, "own/g14/dunst/token", "jochen", "leaked"); err != nil {
// A `by` is a word, never who asked: who asked is the caller the runtime proved (no caller relays another's, ADR 0590).
if _, err := v.Rotate("person.jochen", "own/g14/dunst/token", "someone else", "leaked"); err != nil {
t.Fatal(err)
}
rec, _, _ := v.Store.Read("own/g14/dunst/token")
if rec.ChangedBy != "jochen" || rec.ChangedVia != ControllerCaller || rec.Generation != 2 {
if rec.ChangedBy != "person.jochen" || rec.ChangedVia != "" || rec.Generation != 2 {
t.Errorf("by %q via %q generation %d", rec.ChangedBy, rec.ChangedVia, rec.Generation)
}
}
@@ -491,6 +448,7 @@ func TestTheNamesOfWhatIsTakenIn(t *testing.T) {
// Each verb takes its caller from the runtime, never from its arguments, and names its secret one way only.
func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
v := newVault(t, nil)
v.AskCallers = stdio.Callers{"g14.claude-code"}
env := &Env{Vault: v.Vault}
defs := definitions(env)
var asked []map[string]any
@@ -499,11 +457,11 @@ func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
"plex.plex_issued_credential@anchor": standInProvider(&software)}}).ask
ask := toolNamed(t, defs, Seat+".secret-ask")
args := map[string]any{"node": "anchor", "module": "telegram", "name": "telegram-token", "desk": "laptop",
"asked_by": "g14.claude-code", "caller": ControllerCaller}
"caller": "ace.claude-code"}
if _, err := ask.Run(args); err == nil {
t.Error("an ask opened for a caller named in the arguments")
}
answer, err := ask.runAs(ControllerCaller, args)
answer, err := ask.runAs("g14.claude-code", args)
if err != nil || answer.(Asked).Outcome != "given" || len(asked) != 1 {
t.Fatalf("secret-ask: %+v %v", answer, err)
}
@@ -512,18 +470,6 @@ func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
t.Fatalf("secret_asks: %v %v", listed, err)
}
accept := toolNamed(t, defs, Seat+".accept")
if _, err := accept.runAs(ControllerCaller, map[string]any{"node": "g14", "provision": "postgres"}); err == nil {
t.Error("accept took a name both ways")
}
answer, err = accept.runAs(ControllerCaller, map[string]any{"provider": "novox", "provision": "postgres", "by": "jochen"})
if err != nil {
t.Fatal(err)
}
o := answer.(Opened)
if _, err := accept.runAs(ControllerCaller, map[string]any{"call": o.Call, "sealed": sealToCall(t, o.SealTo, "pw")}); err != nil {
t.Fatal(err)
}
rotate := toolNamed(t, defs, Seat+".rotate")
if _, err := rotate.runAs("g14.claude-code", map[string]any{"node": "g14", "module": "dunst", "name": "token"}); err == nil {
t.Error("a secret not held was rotated through the verb")
@@ -547,7 +493,8 @@ func TestTheVerbsTakeTheirCallerFromTheRuntime(t *testing.T) {
// With the lock held elsewhere, AskEnd waits, and writes nothing until it is let go.
func TestTheEndOfAnAskIsWrittenUnderTheRecordsLock(t *testing.T) {
v := newVault(t, nil)
opened, err := v.AskOpen(ControllerCaller, telegramAsk("g14.claude-code"))
v.AskCallers = stdio.Callers{"g14.claude-code"}
opened, err := v.AskOpen("g14.claude-code", telegramAsk("g14.claude-code"))
if err != nil {
t.Fatal(err)
}
@@ -562,7 +509,7 @@ func TestTheEndOfAnAskIsWrittenUnderTheRecordsLock(t *testing.T) {
v.Store.asksMu.Lock()
done := make(chan error, 1)
go func() {
_, err := v.AskEnd(ControllerCaller, opened.Call, "", "dismissed")
_, err := v.AskEnd("g14.claude-code", opened.Call, "", "dismissed")
done <- err
}()
select {
@@ -87,13 +87,11 @@ func eventsNamed(v testVault, from int, name string) int {
// ADR 0374, issue 787): kept as that module's own secret on that machine, served to the controller's core get and to
// the moved module's client, replaced in place on a re-issue with `secret-changed`, and never made anew by the vault.
// handIssued keeps a covered account as the controller's accept kept one with origin issued (accept went with the
// controller, ADR 0590): what the vault holds of it is served, hashed and never made anew.
func handIssued(t *testing.T, v testVault, caller, secret, value string) (Changed, error) {
t.Helper()
opened, err := v.AcceptOpen(caller, secret, "module issue", OriginIssued)
if err != nil {
return Changed{}, err
}
return v.AcceptTake(caller, opened.Call, sealToCall(t, opened.SealTo, value))
return v.keepWith(secret, value, "module issue", caller, OriginIssued, keepOptions{refuse: v.vaultMinted})
}
func TestAHandedBusAccountIsKeptServedReplacedAndNeverMadeByTheVault(t *testing.T) {
@@ -142,19 +140,6 @@ func TestAHandedBusAccountIsKeptServedReplacedAndNeverMadeByTheVault(t *testing.
t.Errorf("a refused rotation changed the value to %q", got)
}
// A secret that is not a covered account is not handed with origin issued; and only the controller hands one.
if _, err := v.AcceptOpen(ControllerCaller, "own/g14/moved-bot/token", "x", OriginIssued); err == nil {
t.Error("origin issued was taken for a secret that is not a bus account")
}
if _, err := v.AcceptOpen(ControllerCaller, "provision/anchor/postgres", "x", OriginIssued); err == nil {
t.Error("origin issued was taken for a provision's credential")
}
if _, err := v.AcceptOpen("g14.moved-bot", moved, "x", OriginIssued); err == nil {
t.Error("a caller that is not the controller handed a covered account")
}
if _, err := v.AcceptOpen(ControllerCaller, moved, "x", "made"); err == nil {
t.Error("an origin accept does not take was taken")
}
if strings.Contains(everything(t, v), "first-pw") || strings.Contains(everything(t, v), "second-pw") {
t.Fatal("a handed account is in something the vault said or wrote in the clear")
}
+5 -6
View File
@@ -372,9 +372,8 @@ func (v *Vault) IssueAccount(caller, user string) (any, error) {
if user == "" {
return nil, errors.New("issue-account names a bus user (`user`), e.g. ace.mesh-build-go")
}
if nodeOrController(user) {
return nil, fmt.Errorf("%s is never minted by the vault: a node's account and the controller's keep the hash they "+
"have (ADR 0561 §4)", user)
if nodeAccount(user) {
return nil, fmt.Errorf("%s is never minted by the vault: a node's account keeps the hash it has (ADR 0561 §4)", user)
}
raw, found, err := []byte(nil), false, error(nil)
if v.WantRead != nil {
@@ -417,7 +416,7 @@ func (v *Vault) wanted(user string) (buscompose.WantedAccount, bool) {
return w, ok
}
// nodeOrController says user is a node's account or the controller's: never minted here (ADR 0561 §4, §9).
func nodeOrController(user string) bool {
return user == ControllerCaller || strings.HasPrefix(user, "node.")
// nodeAccount says user is a node's account: never minted here (ADR 0561 §4, §9).
func nodeAccount(user string) bool {
return strings.HasPrefix(user, "node.")
}
@@ -161,8 +161,7 @@ func TestIssueAccountMintsOneWantedUserNowAndRefusesTheRest(t *testing.T) {
return nil, false, nil
}
for user, says := range map[string]string{
"node.ace": "never minted",
"controller": "never minted",
"node.ace": "never minted",
} {
if _, err := v.IssueAccount("person.jochen", user); err == nil || !strings.Contains(err.Error(), says) {
t.Errorf("%s: %v, want a refusal saying %q", user, err, says)
@@ -10,6 +10,7 @@ import (
"encoding/base64"
"encoding/json"
"errors"
stdio "git.novox.be/novox/mesh-sdk/go"
"strings"
"sync"
"testing"
@@ -93,15 +94,16 @@ func aDesk(typed string, asked *[]map[string]any) func(string, map[string]any) (
// is typed opened here alone; what crossed is a key and a sealed box.
func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T) {
v := newVault(t, AllowList{"anchor.node-provisioner": {"own/anchor/telegram/telegram-token"}})
v.AskCallers = stdio.Callers{"g14.claude-code"}
var asked []map[string]any
mesh := &aMesh{desk: aDesk(typedAtTheDesk, &asked)}
v.Ask = mesh.ask
got, err := v.AskAtDesk(ControllerCaller, telegramAsk("g14/claude-code, via the console"))
got, err := v.AskAtDesk("g14.claude-code", telegramAsk("g14/claude-code, via the console"))
if err != nil {
t.Fatal(err)
}
if got.Outcome != "given" || got.Generation != 1 || got.AskedBy != "g14/claude-code" ||
if got.Outcome != "given" || got.Generation != 1 || got.AskedBy != "g14.claude-code" ||
got.Secret != "own/anchor/telegram/telegram-token" {
t.Errorf("answered %+v", got)
}
@@ -109,7 +111,7 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
t.Fatalf("the vault asked %v", mesh.keys)
}
p := asked[0]
if p["asked_by"] != "g14/claude-code" || p["module"] != "telegram" || p["secret"] != "telegram-token" ||
if p["asked_by"] != "g14.claude-code" || p["module"] != "telegram" || p["secret"] != "telegram-token" ||
p["node"] != "anchor" || p["seal_to"] == "" || p["timeout_seconds"] != float64(DeskPromptFor) {
t.Errorf("the prompt was asked %v", p)
}
@@ -117,7 +119,7 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
t.Errorf("the vault holds %q", v)
}
rec, _, _ := v.Store.Read("own/anchor/telegram/telegram-token")
if rec.Origin != OriginTyped || rec.ChangedBy != "g14/claude-code" || rec.ChangedVia != ControllerCaller {
if rec.Origin != OriginTyped || rec.ChangedBy != "g14.claude-code" || rec.ChangedVia != "" {
t.Errorf("the record: origin %q by %q via %q", rec.Origin, rec.ChangedBy, rec.ChangedVia)
}
asks, _ := v.Store.Asks(10)
@@ -131,9 +133,10 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
// Dismissed and timed out end the ask so, and keep nothing.
for typed, outcome := range map[string]string{"": "dismissed", "\x00timeout": "timed-out"} {
w := newVault(t, nil)
w.AskCallers = stdio.Callers{"g14.claude-code"}
var a []map[string]any
w.Ask = (&aMesh{desk: aDesk(typed, &a)}).ask
got, err := w.AskAtDesk(ControllerCaller, telegramAsk("g14.claude-code"))
got, err := w.AskAtDesk("g14.claude-code", telegramAsk("g14.claude-code"))
if err != nil || got.Outcome != outcome {
t.Errorf("%s: %+v %v", outcome, got, err)
}
@@ -146,15 +149,17 @@ func TestTheVaultAsksTheDesksPromptItselfAndOnlyASealedValueCrosses(t *testing.T
}
// Answered empty: the operator's answer, said so, nothing kept.
e := newVault(t, nil)
e.AskCallers = stdio.Callers{"g14.claude-code"}
var ea []map[string]any
e.Ask = (&aMesh{desk: aDesk(" ", &ea)}).ask
if got, err := e.AskAtDesk(ControllerCaller, telegramAsk("g14.claude-code")); err != nil || got.Outcome != "empty" {
if got, err := e.AskAtDesk("g14.claude-code", telegramAsk("g14.claude-code")); err != nil || got.Outcome != "empty" {
t.Errorf("empty: %+v %v", got, err)
}
// The desk cannot be asked: refused, recorded so.
r := newVault(t, nil)
r.AskCallers = stdio.Callers{"g14.claude-code"}
r.Ask = (&aMesh{}).ask
if _, err := r.AskAtDesk(ControllerCaller, telegramAsk("g14.claude-code")); err == nil {
if _, err := r.AskAtDesk("g14.claude-code", telegramAsk("g14.claude-code")); err == nil {
t.Error("an ask nobody answered was said to be done")
}
if asks, _ := r.Store.Asks(1); asks[0].Outcome != "refused" {
@@ -153,13 +153,10 @@ func TestAProviderIssuesAValueForEachHolderAndEachHolderGetsOnlyItsOwn(t *testin
moved.Holders[1].Generation != 2 {
t.Errorf("renewed: %+v %v", moved, err)
}
// A holder's value is issued by the software: never rotated, never accepted.
// A holder's value is issued by the software: never rotated.
if _, err := v.Rotate(ControllerCaller, laptopsToken, "jochen", "test"); err == nil {
t.Error("a holder's issued value was rotated")
}
if _, err := v.AcceptOpen(ControllerCaller, laptopsToken, "jochen", ""); err == nil {
t.Error("a holder's issued value was opened for accept")
}
}
// A holder the tool was not asked about, a holder twice, a value not sealed to the call, or an answer of both
@@ -35,11 +35,11 @@ func (v *Vault) LoadedListHeard(revision uint64) {
}
// BeginRotation records a rotation of user's account at the loaded list's revision, under the one rule: none for a
// node's account or the controller's, none while minting is off, none of an account the vault did not make or was not
// node's account, none while minting is off, none of an account the vault did not make or was not
// handed, none twice for one account, and none when another began at this revision.
func (v *Vault) BeginRotation(user string) (Rotating, error) {
if nodeOrController(user) {
return Rotating{}, fmt.Errorf("%s does not rotate here: a node's account and the controller's keep the hash they have (ADR 0561 §4)", user)
if nodeAccount(user) {
return Rotating{}, fmt.Errorf("%s does not rotate here: a node's account keeps the hash it has (ADR 0561 §4)", user)
}
v.minting.mu.Lock()
revision := v.minting.loadedRevision
@@ -62,7 +62,7 @@ func JudgeSensitive(read ActReader, now func() time.Time, caller, verb, act stri
return fmt.Errorf("%s: the hand act %s was recorded by %s, not by %s who asks", verb, act, a.By, caller)
case now().Sub(a.At) > HandActWithin:
return fmt.Errorf("%s: the hand act %s is older than %s: record a new one", verb, act, HandActWithin)
case a.Verb != verb && !strings.HasSuffix(a.Verb, "."+verb):
case a.Verb != Seat+"."+verb:
return fmt.Errorf("%s: the hand act %s is %q: it warrants no %s", verb, act, a.Verb, verb)
}
// mesh-hand-acts records one what; an act for several names lists them space-separated, so each word is a name.
@@ -13,9 +13,12 @@ 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"}},
"old": {ID: "old", By: "novox.mesh-healers", At: now.Add(-25 * time.Hour), Verb: "mesh-vault.rotate", Args: []string{"own/g14/app/key"}},
"other": {ID: "other", By: "novox.mesh-config", At: now.Add(-time.Hour), Verb: "mesh-vault.rotate", Args: []string{"own/g14/app/key"}},
"del": {ID: "del", By: "novox.mesh-healers", At: now.Add(-time.Hour), Verb: "mesh-vault.provisioner_delete", Args: []string{"d-app"}},
// Review 1: a verb of another seat or module that ends in the vault verb's name warrants nothing.
"alike": {ID: "alike", By: "novox.mesh-healers", At: now.Add(-time.Hour), Verb: "other-module.rotate", Args: []string{"own/g14/app/key"}},
"bare": {ID: "bare", By: "novox.mesh-healers", At: now.Add(-time.Hour), Verb: "rotate", Args: []string{"own/g14/app/key"}},
// As mesh-hand-acts records nox's act: one what, the consumers space-separated (review 1 of issue 1126).
"two": {ID: "two", By: "shanks.node-tools", At: now.Add(-time.Minute), Verb: "mesh-vault.provisioner_retire_approve", Args: []string{"c1 c2"}},
}
@@ -43,6 +46,8 @@ func TestEachSensitiveVerbJudgesItsOwnCaller(t *testing.T) {
{"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"}, ""},
{"an act of another seat's rotate", "novox.mesh-healers", SensitiveRotate, "alike", []string{"own/g14/app/key"}, "warrants no rotate"},
{"an act naming the verb without its seat", "novox.mesh-healers", SensitiveRotate, "bare", []string{"own/g14/app/key"}, "warrants no rotate"},
{"one act for two consumers", "shanks.node-tools", SensitiveRetireApprove, "two", []string{"c1", "c2"}, ""},
{"one act naming two, asked for a third", "shanks.node-tools", SensitiveRetireApprove, "two", []string{"c1", "c3"}, "not c3"},
} {
+3 -31
View File
@@ -93,36 +93,9 @@ func definitions(env *Env) []toolDef {
key, _ := a["public_key"].(string)
return env.Vault.Get(caller, strings.TrimSpace(name), key)
}},
{Name: Seat + ".accept",
Description: "Take a value given at the controller's terminal (ADR 0349 clause 8), in two steps from the " +
"controller alone: name the secret (node, module and name for a module's own; provider and provision " +
"for a provision's one credential) and get a call and a key made for it; then send the call and the " +
"value sealed to that key. The vault alone opens it, and never remakes an accepted credential. With " +
"origin issued it keeps a covered bus account the controller minted, replacing it in place.",
Input: intakeInput(map[string]string{"node": "the machine, for a module's own secret",
"module": "the module, for a module's own secret", "name": "the secret's name in the module",
"provider": "the provider's machine, for a provision's credential", "provision": "the provision",
"by": "who gave it at the terminal, as the controller names them",
"origin": "issued for a bus account the controller minted for a core or moved module and hands at " +
"once (ADR 0374, issue 787), kept as that module's own secret and replaced in place on a " +
"re-issue; left out for a value given at the terminal"}),
run: func(caller string, a map[string]any) (any, error) {
v, err := env.ready()
if err != nil {
return nil, err
}
if call := arg(a, "call"); call != "" {
return v.AcceptTake(caller, call, arg(a, "sealed"))
}
secret, err := secretNamed(a)
if err != nil {
return nil, err
}
return v.AcceptOpen(caller, secret, arg(a, "by"), arg(a, "origin"))
}},
{Name: Seat + ".secret-ask",
Description: "Ask the operator for a module's own secret at a desk (ADR 0349 clause 9, ADR 0277 §22–§27), " +
"asked by the controller (naming asked_by) or by a caller the vault's manifest names, itself named by the " +
"asked by a caller the vault's manifest names, itself named by the " +
"bus (ADR 0563 §8): node, module, name and desk. The ask is recorded before any " +
"prompt opens — one open ask per secret and three an hour — and the vault asks the desk's prompt itself, " +
"in its own name, with a key made for the call; what is typed opens in the vault alone. Answers how the " +
@@ -130,15 +103,14 @@ func definitions(env *Env) []toolDef {
"the value.",
Input: map[string]any{"type": "object", "properties": map[string]any{
"node": str("the machine the secret is for"), "module": str("the module"),
"name": str("the secret's name in the module"), "desk": str("the machine whose prompt asks"),
"asked_by": str("from the controller alone: who asked it, as the bus named its caller")}},
"name": str("the secret's name in the module"), "desk": str("the machine whose prompt asks")}},
run: func(caller string, a map[string]any) (any, error) {
v, err := env.ready()
if err != nil {
return nil, err
}
return v.AskAtDesk(caller, AskOf{Node: arg(a, "node"), Module: arg(a, "module"), Name: arg(a, "name"),
Desk: arg(a, "desk"), AskedBy: arg(a, "asked_by")})
Desk: arg(a, "desk")})
}},
{Name: Seat + ".issued",
Description: "Take the credential a provider's software issued for a provision (ADR 0349 clause 10, ADR 0305), " +
@@ -370,15 +370,8 @@ func (v *Vault) Get(caller, name, publicKey string) (Answer, error) {
_ = v.recordHanding(Handing{To: caller, Name: name, Outcome: "refused", Why: why})
return Answer{}, fmt.Errorf("%s may not ask for %s: %s", caller, name, why)
}
// The core's own credentials, to the controller alone, sealed only to their machine's key (ADR 0349 §16).
if isLogin {
// Judged above: the node's own engine, sealed to its own key.
} else if caller == ControllerCaller {
if why := v.coreRefusal(name, publicKey); why != "" {
v.log("refused %s to %s: %s", name, caller, why)
_ = v.recordHanding(Handing{To: caller, Name: name, Outcome: "refused", Why: why})
return Answer{}, fmt.Errorf("%s may not ask for %s: %s", caller, name, why)
}
} else if ok, why := askers.May(caller, name); !ok {
if reason, notYet := strings.CutPrefix(why, notYetPrefix); notYet {
// Not a refusal (issue 928): the client waits and asks again, and nothing is recorded against it.
-1
View File
@@ -198,7 +198,6 @@
"scope": "mesh",
"serves": [
"get",
"accept",
"secret-ask",
"issued",
"rotate",