{"record":{"id":"4300f5ba83db9fd5","repo":"juanfont/headscale","slug":"invalid-auth-key-w","errorCode":null,"errorMessage":"invalid auth key: %w","messagePattern":"invalid auth key: %w","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"hscontrol/db/preauth_keys.go","lineNumber":227,"sourceCode":"\t\tprefixAndHash,\n\t\tauthKeyPrefixLength,\n\t\tauthKeyLength,\n\t\tErrPreAuthKeyFailedToParse,\n\t)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Look up key by prefix\n\terr = tx.Preload(\"User\").First(&pak, \"prefix = ?\", prefix).Error\n\tif err != nil {\n\t\treturn nil, ErrPreAuthKeyNotFound\n\t}\n\n\t// Verify hash matches\n\terr = bcrypt.CompareHashAndPassword(pak.Hash, []byte(hash))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid auth key: %w\", err)\n\t}\n\n\treturn &pak, nil\n}\n\n// parsePrefixedKey splits the prefix-and-secret portion of a new-format key\n// (the part after the \"hskey-*-\" prefix) into its fixed-length prefix and\n// secret components, validating the length, separator position, and that both\n// components are base64 URL-safe. Fixed-length parsing is used instead of\n// separator-based to handle dashes in base64 URL-safe characters.\nfunc parsePrefixedKey(\n\tprefixAndSecret string,\n\t//nolint:unparam // kept explicit though every credential kind uses a 12-char prefix and 64-char secret today\n\tprefixLen, secretLen int,\n\tparseErr error,\n) (string, string, error) {\n\texpectedMinLength := prefixLen + 1 + secretLen\n\tif len(prefixAndSecret) < expectedMinLength {","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/preauth_keys.go#L209-L245","documentation":"The pre-auth key prefix matched a row in the database, but bcrypt comparison of the stored hash against the supplied secret failed. Headscale stores only a bcrypt hash of the key secret; a mismatch means the secret portion of the key is wrong for that prefix. This is an authentication failure for the presented credential, and the raw bcrypt error is wrapped for context.","triggerScenarios":"A node or CLI submits an authkey whose 'hskey-<kind>-<prefix>-<secret>' secret does not match the stored hash: typo/truncation when copying the key, key regenerated with the same prefix (extremely unlikely), or a forged key that guessed an existing 12-char prefix. Produced by GetPreAuthKey during registration (noise auth flow).","commonSituations":"Users copy the key with a missing/extra character, line-wrapping breaks the key in terminals or chat clients, or the key was rotated/deleted and an old copy is reused. Also happens when the 'hskey-' prefix scheme is stripped and the remainder is misassembled.","solutions":["Regenerate the pre-auth key (headscale preauthkeys create) and use the fresh full key string verbatim.","Verify the key was copied without truncation: new-format keys are 'hskey-<kind>-<12-char prefix>-<64-char secret>'.","Confirm the key still exists and is not expired: list keys with 'headscale preauthkeys list <user>'.","Check that no whitespace, quotes, or shell expansion ($ characters historically) corrupted the key in scripts."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before submitting, parse and structurally validate the key:\nif _, _, err := db.ParsePreAuthKeyFormat(rawKey); err != nil {\n    return err // malformed before we even hit the DB\n}\n// Structural check only; secret match can only be verified server-side.","typeGuard":null,"tryCatchPattern":"pak, err := h.cfg.DB.GetPreAuthKey(userStr, rawKey)\nif err != nil {\n    if strings.Contains(err.Error(), \"invalid auth key\") {\n        // treat as bad credential: do not retry, prompt for a new key\n    }\n    return err\n}","preventionTips":["Copy keys whole from 'headscale preauthkeys create' output; never retype them.","Store keys in secret managers or single-line env vars, not multi-line files.","Fail fast on the first mismatch — repeated guesses gain nothing since bcrypt hash comparison is deterministic."],"tags":["preauth-key","authentication","bcrypt"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}