Merge pull request 'A kept call holds a command's first word, never the line (issue 397)' (#210) from fix/397-a-kept-call-holds-no-command-line into main

This commit was merged in pull request #210.
This commit is contained in:
2026-10-10 23:23:49 +00:00
2 changed files with 75 additions and 0 deletions
+25
View File
@@ -468,6 +468,31 @@ func kept(args json.RawMessage) json.RawMessage {
if words, ok := v.([]any); ok && len(words) > 0 { if words, ok := v.([]any); ok && len(words) > 0 {
out[k] = []any{words[0], "(the rest given, not kept)"} out[k] = []any{words[0], "(the rest given, not kept)"}
} }
case k == "command":
// The controller's own command line: its first word, never the rest, as a mesh-cli line's
// first word is. `settings set <module> '<value>' --node <node>` through the `command` verb
// put the value itself in this record, which answers anyone who may call the seat (novox/hq
// issue 397). Its words are parted by any whitespace, because the verb's own splitter parts
// them on a space, a tab or a newline, and a line written with tabs is the same line. The
// first word is capped as every other string here is: the record is written before the verb
// judges the line, so one word may be anything a caller sent, a token included.
if !isString {
out[k] = "(given, not kept)"
break
}
words := strings.Fields(s)
if len(words) == 0 {
out[k] = ""
break
}
first := words[0]
if len(first) > 120 {
first = first[:120] + "…"
}
if len(words) > 1 {
first += " (the rest given, not kept)"
}
out[k] = first
case isString && len(s) <= 120: case isString && len(s) <= 120:
out[k] = s out[k] = s
case isString: case isString:
+50
View File
@@ -11,6 +11,7 @@ import (
"sync" "sync"
"testing" "testing"
"time" "time"
"unicode"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
@@ -166,6 +167,55 @@ func TestACallKeepsNoSettingsOrSecrets(t *testing.T) {
} }
} }
// A command line's own words may carry a setting's value or a secret, so only its first word is kept
// — as a mesh-cli line's is (novox/hq issue 397). Each case says the whole of what is kept, because
// what matters is as much what is left out as what is there.
func TestACallKeepsNoCommandLine(t *testing.T) {
long := strings.Repeat("s3cret", 50) // one word, 300 bytes: a token, as far as this can tell
for line, want := range map[string]string{
`settings set notes 'the operator's own passphrase' --node laptop`: "settings (the rest given, not kept)",
`settings set notes --values {"token":"s3cret"}`: "settings (the rest given, not kept)",
// Parted by a tab or a newline, which the verb's splitter reads as this line's words too.
"settings\tset\tnotes\tthe-operators-passphrase": "settings (the rest given, not kept)",
"builds\n--limit 5": "builds (the rest given, not kept)",
"builds\t--limit 5": "builds (the rest given, not kept)",
// Its first word is kept, and the spaces before it are not part of it.
` node show laptop`: "node (the rest given, not kept)",
// A command of one word is kept whole: there is nothing after it to withhold.
`builds`: "builds",
// One word is still capped, as every other string in a kept record is.
long: long[:120] + "…",
} {
raw, err := json.Marshal(map[string]any{"command": line})
if err != nil {
t.Fatal(err)
}
var got struct{ Command string }
if err := json.Unmarshal(kept(raw), &got); err != nil {
t.Fatal(err)
}
if got.Command != want {
t.Errorf("%q is kept as %q; want %q", line, got.Command, want)
}
// Whole words, so that "set" can be looked for although "settings" is kept, and "show" cannot
// be found in a word such as "shown".
for _, never := range []string{"set", "notes", "laptop", "show", "passphrase", "operators"} {
for _, word := range strings.FieldsFunc(got.Command, func(r rune) bool { return !unicode.IsLetter(r) }) {
if word == never {
t.Errorf("%q is kept as %q: it carries the word %q", line, got.Command, never)
}
}
}
}
// Not a string, so its first word cannot be taken: none of it is kept. The verb refuses such a
// call, but the record is written before it judges it.
raw, _ := json.Marshal(map[string]any{"command": []string{"settings", "set", "notes", "s3cret"}})
if got := string(kept(raw)); strings.Contains(got, "s3cret") {
t.Errorf("kept %s", got)
}
}
// Only the newest KeptCalls are kept. // Only the newest KeptCalls are kept.
func TestTheLogKeepsTheNewest(t *testing.T) { func TestTheLogKeepsTheNewest(t *testing.T) {
l := NewCallLog() l := NewCallLog()