A settings proposal reads at a glance, its values whole where the sender is proven (issues 383 and 639)

The operator could not read the settings proposals on the phone: the change, the fingerprint and a how-to
paragraph in one wall of plain text, and the values masked as ‹path› and ‹address›, so the approval was
blind.

The ask is now a summary of what changes where ("Changes 3 setting(s) of mounts on shanks; approved, the
layer is set at once and shanks takes it at its next push."), then at most three points, one key each (more
keys: the first two and the names of the rest). Who proposed it, the fingerprint and how to read it whole
are its Details, which the router notes after the answers. The change is carried twice: under the content
rule, and whole (asks.Ask.Whole), shown only on a channel that proves who answers, a value shaped like a
secret withheld in both (from the earlier work on issue 383, #202).

Vendors mesh-sdk at 9588871 (fix/639-an-ask-reads-at-a-glance: Message.Note, asks.Glance), and captures its
conformance events at that commit.
This commit is contained in:
jochen
2026-10-13 20:30:56 +02:00
parent 9f3aa91e81
commit f3a3545bd0
9 changed files with 388 additions and 83 deletions
+56 -11
View File
@@ -80,6 +80,57 @@ func Check(text string, machines ...string) (Refusal, bool) {
return Refusal{"address", "a hardware address"}, false
case reIPv6.MatchString(text):
return Refusal{"address", "an IPv6 address"}, false
}
if refusal, ok := secretShape(text); !ok {
return refusal, false
}
if reWinPath.MatchString(text) {
return Refusal{"path", "a drive path"}, false
}
if refusal, ok := randomRun(text); !ok {
return refusal, false
}
for _, word := range strings.FieldsFunc(text, isSeparator) {
w := strings.TrimRight(word, ".:!?")
if isPath(w) {
return Refusal{"path", "a file path"}, false
}
if isHostName(w) {
return Refusal{"address", "a host name"}, false
}
}
return Refusal{}, true
}
// Secret says whether a text carries a secret's shape, the one class that leaves the mesh nowhere — the
// messenger's CheckSecret, one for one. It is what an ask's whole words (asks.Ask.Whole, novox/hq issue 383)
// are held to: on a channel that proves who answers, a path or an address is what the operator approves and
// is shown; a secret never is. A path is read as one: a run with a slash in it is judged piece by piece
// between the slashes, so "/mnt/Photos_2024/Jochen" is a path and not a random string (the review of
// 2026-10-10); the named shapes hold whatever the run holds.
func Secret(text string, machines ...string) (Refusal, bool) {
text = withoutMachines(text, machines)
if refusal, ok := secretShape(text); !ok {
return refusal, false
}
return randomRunOutsidePaths(text)
}
// randomRunOutsidePaths is randomRun with each run that holds a slash judged by its pieces between the slashes.
func randomRunOutsidePaths(text string) (Refusal, bool) {
for _, run := range reRun.FindAllString(text, -1) {
for _, piece := range strings.Split(run, "/") {
if len(piece) >= 20 && looksRandom(piece) {
return Refusal{"secret", "a long random-looking string"}, false
}
}
}
return Refusal{}, true
}
// secretShape is the secret shapes a pattern names.
func secretShape(text string) (Refusal, bool) {
switch {
case rePEM.MatchString(text):
return Refusal{"secret", "a key block"}, false
case reJWT.MatchString(text):
@@ -92,23 +143,17 @@ func Check(text string, machines ...string) (Refusal, bool) {
return Refusal{"secret", "a value given to a secret's name"}, false
case reHex.MatchString(text):
return Refusal{"secret", "a long hexadecimal string"}, false
case reWinPath.MatchString(text):
return Refusal{"path", "a drive path"}, false
}
return Refusal{}, true
}
// randomRun is a long unbroken run of characters spread as a random string's are.
func randomRun(text string) (Refusal, bool) {
for _, run := range reRun.FindAllString(text, -1) {
if looksRandom(run) {
return Refusal{"secret", "a long random-looking string"}, false
}
}
for _, word := range strings.FieldsFunc(text, isSeparator) {
w := strings.TrimRight(word, ".:!?")
if isPath(w) {
return Refusal{"path", "a file path"}, false
}
if isHostName(w) {
return Refusal{"address", "a host name"}, false
}
}
return Refusal{}, true
}
+37
View File
@@ -86,3 +86,40 @@ func TestScrubAlwaysGivesWhatMayLeave(t *testing.T) {
t.Errorf("a text that passes was changed: %q", got)
}
}
// Secret is the one class that leaves the mesh nowhere (novox/hq issue 383): it refuses every secret's shape
// Check refuses, and nothing Check refuses as an address or a path — those an ask's whole words may carry.
func TestSecretRefusesOnlyASecretsShape(t *testing.T) {
for _, text := range []string{
"-----BEGIN RSA PRIVATE KEY-----",
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.abc",
"123456789:ABCdefGHIjklMNOpqrSTUvwxYZ0123456789ab",
"ghp_abcdefghijklmnopqrstuvwxyz0123456789",
"password=hunter2",
"0123456789abcdef0123456789abcdef",
"a key xK9mQ2vL8pR4tW7yB3nF6hJ1dG5sA0zC",
} {
r, ok := Secret(text)
if ok || r.Class != "secret" {
t.Errorf("not refused as a secret: %q (%s)", text, r)
}
}
for _, text := range []string{
"recalbox=smb://nas.lan/recalbox@/mnt/recalbox:ro",
"library=/mnt/library",
"photos=/mnt/Photos_2024/Jochen",
"games=smb://nas.lan/Recalbox_Games2024",
"/srv/media/Series_Archive/Season01",
"10.77.0.9:53",
"jochen@example.com",
"C:\\Users\\jo",
"anchor and the laptop",
} {
if r, ok := Secret(text, "anchor"); !ok {
t.Errorf("refused as %s: %q", r, text)
}
if _, ok := Check(text, "anchor"); ok && text != "anchor and the laptop" {
t.Errorf("Check lets %q leave, so Secret is not the narrower rule", text)
}
}
}