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
95 lines
4.0 KiB
Go
95 lines
4.0 KiB
Go
package main
|
|
|
|
// **Each sensitive verb judges its own caller** (novox/hq ADR 0590 §8, issue 1126). The controller judged the
|
|
// operator's account for these verbs (ADR 0272 §6, §9) and relayed who asked at its terminal; it is not placed, and
|
|
// nox and every local user reach the vault through the mesh MCP server on their machine. So each verb that approves or
|
|
// deletes a retirement, rotates a secret or exports sealed copies answers only:
|
|
//
|
|
// - the operator's account: a caller the runtime proves as `person.<name>`; or
|
|
// - a recorded hand act: any other proven caller names (`act`) the hand act it recorded through mesh-hand-acts for
|
|
// this verb, read back here by its id: recorded by that same caller, within HandActWithin, for this verb, and
|
|
// naming what it acts on when the verb acts on something named (several names in one act's what, space-separated).
|
|
//
|
|
// Nothing else passes: an unproven caller, a module with no act, an act of another caller, verb or subject. A `by`
|
|
// argument is a word, never the proof (actOf keeps it beside the proven caller).
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// PersonPrefix begins the bus user of a person's account: `person.<name>`.
|
|
const PersonPrefix = "person."
|
|
|
|
// The sensitive verbs, as a hand act names them.
|
|
const (
|
|
SensitiveRotate = "rotate"
|
|
SensitiveRetireApprove = "provisioner_retire_approve"
|
|
SensitiveDelete = "provisioner_delete"
|
|
SensitiveExport = "secret_export"
|
|
SensitiveVaultExport = "secret_vault_export"
|
|
)
|
|
|
|
// JudgeSensitive answers whether caller may do verb on about (the names it acts on, each of which a hand act must name;
|
|
// none for an export): nil for the operator's account, or for a hand act of caller's own that warrants it.
|
|
func JudgeSensitive(read ActReader, now func() time.Time, caller, verb, act string, about ...string) error {
|
|
caller = strings.TrimSpace(caller)
|
|
if caller == "" || caller == stdio.Unproven {
|
|
return fmt.Errorf("%s answers a caller the runtime proves, and it proved none (novox/hq ADR 0590 §8)", verb)
|
|
}
|
|
if strings.HasPrefix(caller, PersonPrefix) && len(caller) > len(PersonPrefix) {
|
|
return nil
|
|
}
|
|
if strings.TrimSpace(act) == "" {
|
|
return fmt.Errorf("%s answers the operator's account (person.<name>), or a caller naming the hand act it "+
|
|
"recorded for it through mesh-hand-acts (`act`); %s named none (novox/hq ADR 0590 §8)", verb, caller)
|
|
}
|
|
if read == nil {
|
|
return fmt.Errorf("%s: the vault reads no hand acts, so it answers only the operator's account", verb)
|
|
}
|
|
a, err := read(act)
|
|
if err != nil {
|
|
return fmt.Errorf("%s: the hand act %s could not be read back from mesh-hand-acts: %w", verb, act, err)
|
|
}
|
|
switch {
|
|
case a == nil:
|
|
return fmt.Errorf("%s: mesh-hand-acts holds no hand act %s: record one first", verb, act)
|
|
case a.By != caller:
|
|
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 != 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.
|
|
var named []string
|
|
for _, arg := range a.Args {
|
|
named = append(named, strings.Fields(arg)...)
|
|
}
|
|
for _, name := range about {
|
|
if !slices.Contains(named, name) {
|
|
return fmt.Errorf("%s: the hand act %s names %s, not %s", verb, act, strings.Join(a.Args, " "), name)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// judge is JudgeSensitive with the vault's reader of hand acts and its clock; with no vault ready, only the operator's
|
|
// account passes.
|
|
func (e *Env) judge(caller, verb string, a map[string]any, about ...string) error {
|
|
var read ActReader
|
|
now := time.Now
|
|
if e != nil && e.Vault != nil {
|
|
read = e.Vault.ReadAct
|
|
if e.Vault.Now != nil {
|
|
now = e.Vault.Now
|
|
}
|
|
}
|
|
act, _ := a["act"].(string)
|
|
return JudgeSensitive(read, now, caller, verb, act, about...)
|
|
}
|