diff --git a/CHANGELOG.md b/CHANGELOG.md index ec44a0859..dc0f72a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- The query translator no longer panics on a label filter after `| json` or `| logfmt` whose label name contains the byte `\x01` (found by fuzzing). The proxy already rejects such a query with Loki's parse error before it reaches the translator, so clients were not affected; translator callers that skip that check now get a plain filter instead of a crash (#761). + ## [2.15.0] - 2026-10-12 ### Fixed diff --git a/internal/translator/parsed_key_variants.go b/internal/translator/parsed_key_variants.go index 96a517d7e..420938cc9 100644 --- a/internal/translator/parsed_key_variants.go +++ b/internal/translator/parsed_key_variants.go @@ -193,8 +193,10 @@ func withParsedKeyVariants(labelFn LabelTranslateFunc, exclude func(string) bool } // A label the translation maps (a known or learned VictoriaLogs field) // names that field; only an unmapped label can be a sanitized key. + // A name holding the bytes that separate and mark the candidate keys + // cannot be encoded as a key list (and is no key a parser produces). if translated != label || strings.HasPrefix(label, "_") || !strings.Contains(label, "_") || - strings.ContainsAny(label, ".-") || exclude(label) { + strings.ContainsAny(label, ".-"+parsedKeySep+extractedMarker) || exclude(label) { return translated } keys := parsedKeyVariants(label) diff --git a/internal/translator/parsed_key_variants_test.go b/internal/translator/parsed_key_variants_test.go index d4de80514..9dfe6f557 100644 --- a/internal/translator/parsed_key_variants_test.go +++ b/internal/translator/parsed_key_variants_test.go @@ -272,3 +272,19 @@ func TestParsedKeyVariantsAreBounded(t *testing.T) { t.Errorf("plain: %v", got) } } + +// A label name holding the bytes that separate and mark the candidate keys is +// translated as a plain name; it used to be split as a key list and panic. +func TestParsedKeyFilterNameWithSeparatorBytes(t *testing.T) { + streamFields := map[string]bool{"app": true} + for _, name := range []string{"\x01a_b", "\x01a_extracted", "\x01\x01a_b", "a_\x00b"} { + query := `{app="x"} | json | ` + name + `="v"` + got, err := TranslateLogQLWithStreamFields(query, nil, streamFields) + if err != nil { + continue + } + if !strings.Contains(got, "filter "+name+`:="v"`) { + t.Errorf("%q translated to %q", query, got) + } + } +} diff --git a/internal/translator/translator.go b/internal/translator/translator.go index 1bbf5ae8a..b69ac9f2c 100644 --- a/internal/translator/translator.go +++ b/internal/translator/translator.go @@ -1394,7 +1394,7 @@ func translateSingleLabelFilter(stage string, labelFn LabelTranslateFunc, caps l } if labelFn != nil { translated := labelFn(label) - if strings.Contains(translated, parsedKeySep) { + if strings.Contains(translated, parsedKeySep) && !strings.Contains(label, parsedKeySep) { return translateParsedKeyFilter(stage, strings.Split(translated, parsedKeySep), value, entry.entry, caps) } label = sanitizeFieldIdentifier(translated)