grpc/grpc-go · error
empty contains is not allowed in StringMatcher
Error message
empty contains is not allowed in StringMatcher
What it means
The Contains variant of StringMatcher also requires a non-empty substring. An empty contains value is rejected at string_matcher.go:124-125 because strings.Contains(anything, "") is always true, making the matcher a no-op.
Solutions
- Provide a non-empty substring in the contains field.
- Remove the matcher entirely if a catch-all was intended.
- Fix the upstream policy source to never emit an empty contains.
Example fix
// before
sm, err := matcher.StringMatcherFromProto(&v3matcherpb.StringMatcher{
MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: ""},
}) // err: empty contains is not allowed
// after
sm, err := matcher.StringMatcherFromProto(&v3matcherpb.StringMatcher{
MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: "internal"},
}) Defensive patterns
Strategy: validation
Validate before calling
func validateStringMatcherProto(p *v3matcherpb.StringMatcher) error {
if p == nil { return errors.New("nil StringMatcher") }
if _, ok := p.GetMatchPattern().(*v3matcherpb.StringMatcher_Contains); ok && p.GetContains() == "" {
return errors.New("StringMatcher.contains must not be empty")
}
return nil
} Prevention
- Never emit `contains: ""`; remove the matcher if a catch-all is intended.
- Lint policy configs for empty contains values.
- Use omitempty in templates so unset matchers are dropped.
When it happens
Trigger: Triggered when an xDS config sets `contains: ""` in a StringMatcher. Encountered while parsing header/path matchers.
Common situations: A control-plane defaulting contains to empty; YAML `contains:` with no value; configuration generated by a template that left contains unset; a ported Envoy config that tolerated the empty value.
Related errors
- empty prefix is not allowed in StringMatcher
- empty suffix is not allowed in StringMatcher
- input StringMatcher proto is nil
- unknown header matcher type
- gcpauthn: cache_config.cache_size must be greater than zero
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/204d79aaf7c6be3d.
Report an issue: GitHub.
Appendix: source
Thrown at internal/xds/matcher/string_matcher.go:125
if matcherProto.GetPrefix() == "" {
return StringMatcher{}, errors.New("empty prefix is not allowed in StringMatcher")
}
matcher.prefixMatch = newStrPtr(&mt.Prefix, matcher.ignoreCase)
case *v3matcherpb.StringMatcher_Suffix:
if matcherProto.GetSuffix() == "" {
return StringMatcher{}, errors.New("empty suffix is not allowed in StringMatcher")
}
matcher.suffixMatch = newStrPtr(&mt.Suffix, matcher.ignoreCase)
case *v3matcherpb.StringMatcher_SafeRegex:
regex := matcherProto.GetSafeRegex().GetRegex()
re, err := CompileSafeRegex(regex)
if err != nil {
return StringMatcher{}, fmt.Errorf("safe_regex matcher %q is invalid", regex)
}
matcher = NewRegexStringMatcher(re)
case *v3matcherpb.StringMatcher_Contains:
if matcherProto.GetContains() == "" {
return StringMatcher{}, errors.New("empty contains is not allowed in StringMatcher")
}
matcher.containsMatch = newStrPtr(&mt.Contains, matcher.ignoreCase)
default:
return StringMatcher{}, fmt.Errorf("unrecognized string matcher: %+v", matcherProto)
}
return matcher, nil
}
// NewExactStringMatcher creates a string matcher that requires the input string
// to exactly match the pattern specified here. The match will be case
// insensitive if ignore_case is true.
func NewExactStringMatcher(pattern string, ignoreCase bool) StringMatcher {
return StringMatcher{
exactMatch: newStrPtr(&pattern, ignoreCase),
ignoreCase: ignoreCase,
}
}
View on GitHub (pinned to 0c51461d27)