{"record":{"id":"5047e6bacc4d4041","repo":"juanfont/headscale","slug":"w-prefix-contains-invalid-characters-expected-b","errorCode":null,"errorMessage":"%w: prefix contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)","messagePattern":"%w: prefix contains invalid characters \\(expected base64 URL-safe: A-Za-z0-9_-\\)","errorType":"validation","errorClass":"parseErr (ErrPreAuthKeyFailedToParse|ErrOAuthClientFailedToParse|ErrAccessTokenFailedToParse|ErrAPIKeyFailedToParse)","httpStatus":401,"severity":"error","filePath":"hscontrol/db/preauth_keys.go","lineNumber":280,"sourceCode":"\t\t\tprefixAndSecret[prefixLen],\n\t\t)\n\t}\n\n\tsecret := prefixAndSecret[prefixLen+1:]\n\n\t// Validate secret length\n\tif len(secret) != secretLen {\n\t\treturn \"\", \"\", fmt.Errorf(\n\t\t\t\"%w: secret length mismatch, expected %d chars, got %d\",\n\t\t\tparseErr,\n\t\t\tsecretLen,\n\t\t\tlen(secret),\n\t\t)\n\t}\n\n\t// Validate prefix contains only base64 URL-safe characters\n\tif !isValidBase64URLSafe(prefix) {\n\t\treturn \"\", \"\", fmt.Errorf(\n\t\t\t\"%w: prefix contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)\",\n\t\t\tparseErr,\n\t\t)\n\t}\n\n\t// Validate secret contains only base64 URL-safe characters\n\tif !isValidBase64URLSafe(secret) {\n\t\treturn \"\", \"\", fmt.Errorf(\n\t\t\t\"%w: secret contains invalid characters (expected base64 URL-safe: A-Za-z0-9_-)\",\n\t\t\tparseErr,\n\t\t)\n\t}\n\n\treturn prefix, secret, nil\n}\n\n// isValidBase64URLSafe reports whether s contains only base64 URL-safe\n// characters (A-Za-z0-9-_). Key material is now generated as hex, a subset of","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/preauth_keys.go#L262-L298","documentation":"The 12-character prefix portion of a parsed credential contains characters outside the base64 URL-safe alphabet (A-Za-z0-9_-). Current headscale generates key material as hex (a subset), so a violation means the prefix was corrupted or is not a headscale key at all. The parseErr sentinel is wrapped for classification.","triggerScenarios":"Passing a non-headscale credential (e.g. a Tailscale authkey 'tskey-auth-...') into headscale's key parser, or a prefix mangled by encoding (spaces, '%', '=' from URL encoding).","commonSituations":"Mixing up headscale and Tailscale keys in configs; URL-encoding or HTML-entity corruption when keys travel through web forms or query parameters.","solutions":["Confirm the key starts with the 'hskey-' scheme and was issued by this headscale instance.","If the key passed through a URL or form, ensure it was not percent-encoded or truncated.","Regenerate the key from the headscale CLI/API and use it directly."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"var base64URLSafe = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)\nfunc validPrefix(p string) bool {\n    return len(p) == 12 && base64URLSafe.MatchString(p)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Confirm the 'hskey-' scheme prefix before parsing — reject foreign keys early.","URL-decode keys exactly once if they travel through query parameters.","Log a SHA of rejected keys, never the key itself, for debugging."],"tags":["preauth-key","validation","key-format","encoding"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}