{"record":{"id":"cecc873ec0ba0a14","repo":"netbirdio/netbird","slug":"user-group-name-cannot-be-empty","errorCode":null,"errorMessage":"user group name cannot be empty","messagePattern":"user group name cannot be empty","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":1499,"sourceCode":"\tswitch r.Mode {\n\tcase ModeHTTP, ModeTCP, ModeUDP, ModeTLS:\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported mode %q\", r.Mode)\n\t}\n\n\tif IsL4Protocol(r.Mode) {\n\t\tif r.Pin != \"\" || r.Password != \"\" || len(r.UserGroups) > 0 {\n\t\t\treturn fmt.Errorf(\"authentication is not supported for %s mode\", r.Mode)\n\t\t}\n\t}\n\n\tif r.Pin != \"\" && !pinRegexp.MatchString(r.Pin) {\n\t\treturn errors.New(\"invalid pin: must be exactly 6 digits\")\n\t}\n\n\tfor _, g := range r.UserGroups {\n\t\tif g == \"\" {\n\t\t\treturn errors.New(\"user group name cannot be empty\")\n\t\t}\n\t}\n\n\tif r.NamePrefix != \"\" && !validNamePrefix.MatchString(r.NamePrefix) {\n\t\treturn fmt.Errorf(\"invalid name prefix %q: must be lowercase alphanumeric with optional hyphens, 1-32 characters\", r.NamePrefix)\n\t}\n\n\treturn nil\n}\n\n// ToService builds a Service from the expose request.\nfunc (r *ExposeServiceRequest) ToService(accountID, peerID, serviceName string) *Service {\n\tsvc := &Service{\n\t\tAccountID: accountID,\n\t\tName:      serviceName,\n\t\tMode:      r.Mode,\n\t\tEnabled:   true,\n\t}","sourceCodeStart":1481,"sourceCodeEnd":1517,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L1481-L1517","documentation":"Returned by ExposeServiceRequest.Validate when the user_groups slice of a peer-expose request contains an empty string. User groups name the distribution groups used for bearer (SSO) token distribution, so an empty name cannot map to any group. Empty entries are almost always client-side parsing artifacts, not intentional input.","triggerScenarios":"Building user_groups with strings.Split(input, \",\") where the input has a trailing comma (\"admins,,devs\" or \"admins,\"); serializing [\"\"] from a form field that was left blank; trimming nothing so \" \" passes but a true empty string fails.","commonSituations":"CLI flags parsed as comma-separated lists without filtering empties. JSON payloads constructed by joining optional fields where one was absent. Copy-paste between requests where a group placeholder was never filled in.","solutions":["Filter out empty entries before sending: only non-empty group names in user_groups.","Trim whitespace from each entry when parsing user input, then drop empties.","Verify each name matches an existing distribution group in the account so the bearer config applies."],"exampleFix":"// before\ngroups := strings.Split(\"admins,\", \",\") // [\"admins\", \"\"]\nreq := ExposeServiceRequest{ Mode: \"http\", Port: 8080, UserGroups: groups }\n\n// after\nvar groups []string\nfor _, g := range strings.Split(raw, \",\") {\n    if g = strings.TrimSpace(g); g != \"\" {\n        groups = append(groups, g)\n    }\n}\nreq := ExposeServiceRequest{ Mode: \"http\", Port: 8080, UserGroups: groups }","handlingStrategy":"validation","validationCode":"func cleanUserGroups(raw []string) ([]string, error) {\n\tvar out []string\n\tfor _, g := range raw {\n\t\tg = strings.TrimSpace(g)\n\t\tif g == \"\" {\n\t\t\treturn nil, errors.New(\"user group name cannot be empty\")\n\t\t}\n\t\tout = append(out, g)\n\t}\n\treturn out, nil\n}","typeGuard":"func hasNoEmptyUserGroups(groups []string) bool {\n\tfor _, g := range groups {\n\t\tif strings.TrimSpace(g) == \"\" {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","tryCatchPattern":"if err := req.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"user group name cannot be empty\") {\n\t\treturn respondBadRequest(errors.New(\"filter empty entries from user_groups (trailing comma?)\"))\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["Trim and filter list inputs before submitting - strings.Split on a trailing comma yields an empty element.","Validate group names against the account's actual distribution groups before the request.","Use JSON schemas with minLength: 1 on array items for user-facing forms."],"tags":["netbird","reverse-proxy","validation","groups","expose","sso","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}