{"record":{"id":"b69462e32b2640e6","repo":"grpc/grpc-go","slug":"empty-prefix-is-not-allowed-in-stringmatcher","errorCode":null,"errorMessage":"empty prefix is not allowed in StringMatcher","messagePattern":"empty prefix is not allowed in StringMatcher","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/matcher/string_matcher.go","lineNumber":108,"sourceCode":"\treturn s\n}\n\n// StringMatcherFromProto is a helper function to create a StringMatcher from\n// the corresponding StringMatcher proto.\n//\n// Returns a non-nil error if matcherProto is invalid.\nfunc StringMatcherFromProto(matcherProto *v3matcherpb.StringMatcher) (StringMatcher, error) {\n\tif matcherProto == nil {\n\t\treturn StringMatcher{}, errors.New(\"input StringMatcher proto is nil\")\n\t}\n\n\tmatcher := StringMatcher{ignoreCase: matcherProto.GetIgnoreCase()}\n\tswitch mt := matcherProto.GetMatchPattern().(type) {\n\tcase *v3matcherpb.StringMatcher_Exact:\n\t\tmatcher.exactMatch = newStrPtr(&mt.Exact, matcher.ignoreCase)\n\tcase *v3matcherpb.StringMatcher_Prefix:\n\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}","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/matcher/string_matcher.go#L90-L126","documentation":"Returned by StringMatcherFromProto (string_matcher.go:107-108) for the Prefix match variant when the prefix string is empty. An empty prefix would match every string and is considered a configuration mistake; callers wanting that behavior should use an explicit exact-empty or present matcher instead.","triggerScenarios":"A StringMatcher proto whose match pattern is StringMatcher_Prefix with Prefix=\"\". The type switch detects the Prefix case, checks matcherProto.GetPrefix()==\"\", and rejects it.","commonSituations":"Control-plane templating that emits prefix matchers with a variable that resolved to empty; an RBAC/route rule intended to match all but authored as an empty prefix.","solutions":["Provide a non-empty prefix value in the StringMatcher config.","If the intent is 'match everything', drop the matcher entirely or use an always-allow permission.","Sanitize generated matchers: reject/omit prefix matchers with empty values before sending to xDS."],"exampleFix":"// before: { \"prefix\": \"\" }\n// after:  { \"prefix\": \"/api/v1/\" }   // or remove the matcher if all-match is intended","handlingStrategy":"validation","validationCode":"func validPrefixStringMatcher(m *v3matcherpb.StringMatcher) error {\n    if m.GetPrefix() == \"\" {\n        return errors.New(\"prefix matcher must have a non-empty prefix\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reject empty prefix values when generating matchers.","Prefer exact matchers when the intent is a precise value, not a wildcard.","Add a config-lint step for RBAC/route matchers before publishing."],"tags":["xds","matcher","string-matcher","config","validation"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}