{"record":{"id":"d284d5c27bc80c89","repo":"netbirdio/netbird","slug":"invalid-pin-must-be-exactly-6-digits","errorCode":null,"errorMessage":"invalid pin: must be exactly 6 digits","messagePattern":"invalid pin: must be exactly 6 digits","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"management/internals/modules/reverseproxy/service/service.go","lineNumber":1494,"sourceCode":"\n\tif r.Port == 0 {\n\t\treturn fmt.Errorf(\"port must be between 1 and 65535, got %d\", r.Port)\n\t}\n\n\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{","sourceCodeStart":1476,"sourceCodeEnd":1512,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/internals/modules/reverseproxy/service/service.go#L1476-L1512","documentation":"Returned by ExposeServiceRequest.Validate when the peer-initiated expose request sets a Pin that does not match ^\\d{6}$ - exactly six ASCII digits. The pin becomes a PINAuthConfig on the resulting service, and the six-digit shape is a product contract for the expose flow. Note the L4 guard earlier in the same function already rejected any pin for tcp/udp/tls modes, so this error can only surface for http-mode expose requests.","triggerScenarios":"Calling the expose API (peer 'netbird expose' path) with pin \"1234\" (4 digits), \"1234567\" (7), \"12 456\" (space), \"abcdef\", or a pin passed as a JSON number where the serializer dropped leading zeros (012345 became \"12345\").","commonSituations":"Porting 4-digit PIN conventions from other products. Client-side types using int for the pin, losing leading zeros and allowing non-6-digit values. Copy-paste with a trailing newline or whitespace inside the pin string.","solutions":["Send the pin as a string of exactly six digits, e.g. \"435678\".","Make the client field a string type and validate with ^\\d{6}$ before submitting; never a number.","Leave pin empty to skip pin auth for the exposed service (other auth like password/user_groups is still available)."],"exampleFix":"// before\nreq := ExposeServiceRequest{ Mode: \"http\", Port: 8080, Pin: \"1234\" }\n\n// after\nreq := ExposeServiceRequest{ Mode: \"http\", Port: 8080, Pin: \"435678\" }","handlingStrategy":"validation","validationCode":"var pinRe = regexp.MustCompile(`^\\d{6}$`)\n\nfunc checkExposePin(mode, pin string) error {\n\tif pin == \"\" {\n\t\treturn nil\n\t}\n\tif pinRe.MatchString(pin) != true {\n\t\treturn errors.New(\"pin must be exactly 6 digits\")\n\t}\n\treturn nil\n}","typeGuard":"func isValidExposePin(pin string) bool {\n\treturn pin == \"\" || pinRe.MatchString(pin)\n}","tryCatchPattern":"if err := req.Validate(); err != nil {\n\tif strings.Contains(err.Error(), \"invalid pin\") {\n\t\treturn respondBadRequest(errors.New(\"send pin as a 6-digit string, e.g. \\\"435678\\\"\"))\n\t}\n\treturn respondBadRequest(err)\n}","preventionTips":["Type the pin as string end to end - never int, or leading zeros disappear.","Validate with ^\\d{6}$ in the client before the API call (CLI arg, form field, JSON schema).","Do not set a pin at all for tcp/udp/tls expose requests - the L4 guard rejects any pin first."],"tags":["netbird","reverse-proxy","validation","auth","pin","expose","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}