Files
meshcore-analyzer/cmd/ingestor/observer_fields_test.go
T
nullrouten0andClaude Mythos 5.1 a981420d21 fix(ingestor): cap observer-supplied string lengths (#2123)
Observer `id` and `iata` come from the MQTT topic; `origin` (name),
`model`, `firmware`, `client_version` and `radio` come from the status
JSON. Any publisher controls them and nothing bounded their length, so
one message could store a 64 KB observer id or name. Each new id is also
a new `observers` row, and that table is joined by most packet queries.

**Fix:** a small `clampObserverField` helper strips control characters
and truncates: ids to 128 runes, text fields to 128, IATA to 16. Applied
on the status path, the packet path and in `extractObserverMeta`. Values
are truncated rather than rejected, so a legitimate observer with a long
name still appears.

**Tests:** `observer_fields_test.go` — short values untouched, long
values cut at 128 runes (not bytes, so multi-byte names are not split),
control characters removed, `extractObserverMeta` caps all string
fields. Full ingestor suite passes.

Running in production on our instance since 2026-10-07.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Mythos 5.1 <noreply@anthropic.com>
2026-10-08 15:58:13 +02:00

48 lines
1.5 KiB
Go

package main
import (
"strings"
"testing"
"unicode/utf8"
)
func TestClampObserverField(t *testing.T) {
if got := clampObserverField("Observer One", 128); got != "Observer One" {
t.Fatalf("short value changed: %q", got)
}
long := strings.Repeat("x", 5000)
if got := clampObserverField(long, 128); utf8.RuneCountInString(got) != 128 {
t.Fatalf("long value not truncated to 128 runes: %d", utf8.RuneCountInString(got))
}
// Truncation counts runes, not bytes, so a multi-byte name is not cut mid-character.
multi := strings.Repeat("é", 200)
got := clampObserverField(multi, 128)
if utf8.RuneCountInString(got) != 128 || !utf8.ValidString(got) {
t.Fatalf("multi-byte truncation broke the string: %d runes valid=%v", utf8.RuneCountInString(got), utf8.ValidString(got))
}
if got := clampObserverField("a\x00b\x1fc", 128); got != "abc" {
t.Fatalf("control characters not stripped: %q", got)
}
}
func TestExtractObserverMetaCapsStrings(t *testing.T) {
long := strings.Repeat("m", 1000)
meta := extractObserverMeta(map[string]interface{}{
"model": long,
"firmware": long,
"client_version": long,
"radio": long,
})
if meta == nil {
t.Fatal("meta nil")
}
for name, v := range map[string]*string{"model": meta.Model, "firmware": meta.Firmware, "client_version": meta.ClientVersion, "radio": meta.Radio} {
if v == nil {
t.Fatalf("%s not set", name)
}
if n := utf8.RuneCountInString(*v); n != maxObserverTextLen {
t.Fatalf("%s not capped: %d runes", name, n)
}
}
}