{"record":{"id":"204d79aaf7c6be3d","repo":"grpc/grpc-go","slug":"empty-contains-is-not-allowed-in-stringmatcher","errorCode":null,"errorMessage":"empty contains is not allowed in StringMatcher","messagePattern":"empty contains is not allowed in StringMatcher","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/matcher/string_matcher.go","lineNumber":125,"sourceCode":"\t\tif matcherProto.GetPrefix() == \"\" {\n\t\t\treturn StringMatcher{}, errors.New(\"empty prefix is not allowed in StringMatcher\")\n\t\t}\n\t\tmatcher.prefixMatch = newStrPtr(&mt.Prefix, matcher.ignoreCase)\n\tcase *v3matcherpb.StringMatcher_Suffix:\n\t\tif matcherProto.GetSuffix() == \"\" {\n\t\t\treturn StringMatcher{}, errors.New(\"empty suffix is not allowed in StringMatcher\")\n\t\t}\n\t\tmatcher.suffixMatch = newStrPtr(&mt.Suffix, matcher.ignoreCase)\n\tcase *v3matcherpb.StringMatcher_SafeRegex:\n\t\tregex := matcherProto.GetSafeRegex().GetRegex()\n\t\tre, err := CompileSafeRegex(regex)\n\t\tif err != nil {\n\t\t\treturn StringMatcher{}, fmt.Errorf(\"safe_regex matcher %q is invalid\", regex)\n\t\t}\n\t\tmatcher = NewRegexStringMatcher(re)\n\tcase *v3matcherpb.StringMatcher_Contains:\n\t\tif matcherProto.GetContains() == \"\" {\n\t\t\treturn StringMatcher{}, errors.New(\"empty contains is not allowed in StringMatcher\")\n\t\t}\n\t\tmatcher.containsMatch = newStrPtr(&mt.Contains, matcher.ignoreCase)\n\tdefault:\n\t\treturn StringMatcher{}, fmt.Errorf(\"unrecognized string matcher: %+v\", matcherProto)\n\t}\n\treturn matcher, nil\n}\n\n// NewExactStringMatcher creates a string matcher that requires the input string\n// to exactly match the pattern specified here. The match will be case\n// insensitive if ignore_case is true.\nfunc NewExactStringMatcher(pattern string, ignoreCase bool) StringMatcher {\n\treturn StringMatcher{\n\t\texactMatch: newStrPtr(&pattern, ignoreCase),\n\t\tignoreCase: ignoreCase,\n\t}\n}\n","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/matcher/string_matcher.go#L107-L143","documentation":"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.","triggerScenarios":"Triggered when an xDS config sets `contains: \"\"` in a StringMatcher. Encountered while parsing header/path matchers.","commonSituations":"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.","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."],"exampleFix":"// before\nsm, err := matcher.StringMatcherFromProto(&v3matcherpb.StringMatcher{\n    MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: \"\"},\n}) // err: empty contains is not allowed\n\n// after\nsm, err := matcher.StringMatcherFromProto(&v3matcherpb.StringMatcher{\n    MatchPattern: &v3matcherpb.StringMatcher_Contains{Contains: \"internal\"},\n})","handlingStrategy":"validation","validationCode":"func validateStringMatcherProto(p *v3matcherpb.StringMatcher) error {\n    if p == nil { return errors.New(\"nil StringMatcher\") }\n    if _, ok := p.GetMatchPattern().(*v3matcherpb.StringMatcher_Contains); ok && p.GetContains() == \"\" {\n        return errors.New(\"StringMatcher.contains must not be empty\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["grpc","xds","matcher","validation","contains"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}