diff --git a/cmd/mesh-controller/desk_secret_test.go b/cmd/mesh-controller/desk_secret_test.go index eef0bc04..c63364fc 100644 --- a/cmd/mesh-controller/desk_secret_test.go +++ b/cmd/mesh-controller/desk_secret_test.go @@ -226,3 +226,22 @@ func TestASecretValueIsNeverAcceptedThroughAVerb(t *testing.T) { } } } + +// The `give` verb's own line passes the terminal-only rule of ADR 0266, and no other `secret accept` does: a +// value, a file, a provider or an extra word is still the terminal's alone. +func TestOnlyTheGiveLinePassesTheTerminalRuleForSecrets(t *testing.T) { + if err := terminalOnly([]string{"secret", "accept", "anchor", "telegram", "telegram-token", "--at-desk", "laptop"}); err != nil { + t.Errorf("give's line refused: %v", err) + } + for _, argv := range [][]string{ + {"secret", "accept", "anchor", "telegram", "telegram-token"}, + {"secret", "accept", "anchor", "telegram", "telegram-token", "--from", "/tmp/x"}, + {"secret", "accept", "anchor", "telegram", "telegram-token", "--at-desk", "laptop", "--local"}, + {"secret", "accept", "anchor", "telegram", "--provider", "--at-desk", "laptop"}, + {"secret", "export", "anchor", "telegram", "telegram-token", "--at-desk", "laptop"}, + } { + if err := terminalOnly(argv); err == nil { + t.Errorf("%v passed the terminal rule", argv) + } + } +} diff --git a/cmd/mesh-controller/seatverbs.go b/cmd/mesh-controller/seatverbs.go index 308fcbbd..48b17938 100644 --- a/cmd/mesh-controller/seatverbs.go +++ b/cmd/mesh-controller/seatverbs.go @@ -1500,6 +1500,20 @@ var terminalOnlyCommands = map[string]string{ "licence": "the licences' secrets", } +// givenAtTheDesk is exactly the line the `give` verb composes, and nothing beside it: `secret accept +// --at-desk `, with no other word — no value, no file, no provider. +func givenAtTheDesk(argv []string) bool { + if len(argv) != 7 || argv[0] != "secret" || argv[1] != "accept" || argv[5] != "--at-desk" { + return false + } + for _, w := range argv[2:5] { + if w == "" || strings.HasPrefix(w, "-") { + return false + } + } + return argv[6] != "" && !strings.HasPrefix(argv[6], "-") +} + // terminalOnly refuses, through any verb, a command that is the operator's at the controller's terminal // alone (novox/hq ADR 0266). **Every `node` subcommand that is not a read**: `node account` and // `node agent-account` above all. Whoever may call a verb includes agents, and an agent that named itself @@ -1513,8 +1527,10 @@ func terminalOnly(argv []string) error { return terminalRefusal("%s is run at the controller's terminal only, never through a verb: it holds %s, and "+ "whoever may call a verb includes agents (novox/hq ADR 0266). Nothing was done", argv[0], what) } - // Of a secret's commands only rotation, which seals the new value to the machine that uses it. - if argv[0] == "secret" && (len(argv) < 2 || argv[1] != "rotate") { + // Of a secret's commands only rotation, which seals the new value to the machine that uses it, and the + // `give` verb's own line: an own secret typed by the operator into the desk's hidden prompt, sealed to this + // call and then to the module's machine, so no value travels in the verb or its answer (hq ADR 0259 §10). + if argv[0] == "secret" && (len(argv) < 2 || argv[1] != "rotate") && !givenAtTheDesk(argv) { return terminalRefusal("secret %s is run at the controller's terminal only, never through a verb: accepting, "+ "recovering or exporting a secret hands it to whoever asks, and that includes agents (novox/hq ADR "+ "0266). Nothing was done", strings.Join(argv[1:], " "))