{"record":{"id":"4d8f75b597a14a0c","repo":"grpc/grpc-go","slug":"empty-suffix-is-not-allowed-in-stringmatcher","errorCode":null,"errorMessage":"empty suffix is not allowed in StringMatcher","messagePattern":"empty suffix is not allowed in StringMatcher","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/matcher/string_matcher.go","lineNumber":113,"sourceCode":"//\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}\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","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/matcher/string_matcher.go#L95-L131","documentation":"Returned by StringMatcherFromProto (string_matcher.go:112-113) for the Suffix match variant when the suffix string is empty. Like the prefix case, an empty suffix would trivially match every input and is treated as a config error.","triggerScenarios":"A StringMatcher proto whose match pattern is StringMatcher_Suffix with Suffix=\"\". The switch detects the Suffix case, checks GetSuffix()==\"\", and returns the error.","commonSituations":"Templated/generated RBAC or route config where a suffix variable resolved to empty; porting a rule that intended a wildcard but used an empty suffix.","solutions":["Set a non-empty suffix value in the StringMatcher.","If all-match is intended, remove the suffix matcher or use an always-allow rule.","Validate generated matchers reject empty suffix values before publishing."],"exampleFix":"// before: { \"suffix\": \"\" }\n// after:  { \"suffix\": \".example.com\" }","handlingStrategy":"validation","validationCode":"func validSuffixStringMatcher(m *v3matcherpb.StringMatcher) error {\n    if m.GetSuffix() == \"\" {\n        return errors.New(\"suffix matcher must have a non-empty suffix\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reject empty suffix values when generating matchers.","Use present/header matchers when the intent is 'any value'.","Lint route/RBAC configs for empty matcher fields."],"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"}