{"record":{"id":"501676d699e9c397","repo":"juanfont/headscale","slug":"username-contains-invalid-character-c","errorCode":null,"errorMessage":"username contains invalid character: '%c'","messagePattern":"username contains invalid character: '%c'","errorType":"validation","errorClass":"ErrUsernameInvalidChar","httpStatus":null,"severity":"error","filePath":"hscontrol/util/dns.go","lineNumber":68,"sourceCode":"\t}\n\n\tatCount := 0\n\n\tfor _, char := range username {\n\t\tswitch {\n\t\tcase unicode.IsLetter(char),\n\t\t\tunicode.IsDigit(char),\n\t\t\tchar == '-',\n\t\t\tchar == '.',\n\t\t\tchar == '_':\n\t\t\t// Valid characters\n\t\tcase char == '@':\n\t\t\tatCount++\n\t\t\tif atCount > 1 {\n\t\t\t\treturn ErrUsernameTooManyAt\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"%w: '%c'\", ErrUsernameInvalidChar, char)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// generateMagicDNSRootDomains generates a list of DNS entries to be included in [tailcfg.DNSConfig.Routes] in [tailcfg.MapResponse].\n// This list of reverse DNS entries instructs the OS on what subnets and domains the Tailscale embedded DNS\n// server (listening in 100.100.100.100 udp/53) should be used for.\n//\n// Tailscale.com includes in the list:\n// - the [types.DNSConfig.BaseDomain] of the user\n// - the reverse DNS entry for IPv6 (0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa., see below more on IPv6)\n// - the reverse DNS entries for the IPv4 subnets covered by the user's `IPPrefix`.\n//   In the public SaaS this is [64-127].100.in-addr.arpa.\n//\n// The main purpose of this function is then generating the list of IPv4 entries. For the 100.64.0.0/10, this\n// is clear, and could be hardcoded. But we are allowing any range as `IPPrefix`, so we need to find out the","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/util/dns.go#L50-L86","documentation":"Returned by the username validator in hscontrol/util when a character in a username is not a letter, digit, '-', '.', '_', or a single '@'. It wraps ErrUsernameInvalidChar. This constrains what can safely become part of MagicDNS names and DNS records derived from usernames.","triggerScenarios":"Validating a username containing characters such as ':', '$', spaces, or a second '@' (the second '@' yields ErrUsernameTooManyAt instead). Happens on OIDC login with an identity-provider profile name containing unusual characters, or when creating users via the API.","commonSituations":"OIDC providers that emit usernames with characters from email display names or external schemas (e.g. 'DOMAIN\\\\user' or 'user+tag'); migrating user databases with legacy character sets.","solutions":["Pre-sanitize usernames before user creation: map disallowed characters to '-' or drop them","If using OIDC, configure the provider to supply a compliant preferred_username claim","Check the exact offending character printed by '%c' and adjust the source-of-truth naming scheme"],"exampleFix":"// before\nusername := \"ad\\\\bob\"\nerr := util.ValidateUsername(username) // invalid char '\\\\'\n// after\nusername := \"ad-bob\"\nerr := util.ValidateUsername(username)","handlingStrategy":"validation","validationCode":"import (\n    \"strings\"\n    \"unicode\"\n)\n\nfunc normalizeUsername(name string) string {\n    var b strings.Builder\n    for _, r := range strings.ToLower(name) {\n        switch {\n        case unicode.IsLetter(r) || unicode.IsDigit(r), r == '-', r == '.', r == '_':\n            b.WriteRune(r)\n        case r == '@':\n            b.WriteRune('-') // or keep single @ if your flow allows\n        default:\n            b.WriteRune('-')\n        }\n    }\n    return b.String()\n}","typeGuard":null,"tryCatchPattern":"if err := util.ValidateUsername(u); err != nil {\n    if errors.Is(err, util.ErrUsernameInvalidChar) {\n        u = normalizeUsername(u) // then re-validate\n    }\n    if errors.Is(err, util.ErrUsernameTooManyAt) {\n        u = strings.Replace(u, \"@\", \"-\", -1)\n    }\n}","preventionTips":["Map OIDC preferred_username through a sanitizer before user creation","Adopt a house style for usernames: letters, digits, '-', '.', '_', one '@'","Validate on both write (API/OIDC) and read paths when migrating user stores"],"tags":["username","dns","validation","oidc"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}