{"record":{"id":"3f7152b66733ef58","repo":"juanfont/headscale","slug":"auth-id-has-invalid-length","errorCode":null,"errorMessage":"auth ID has invalid length","messagePattern":"auth ID has invalid length","errorType":"validation","errorClass":"ErrInvalidAuthIDLength","httpStatus":400,"severity":"error","filePath":"hscontrol/types/common.go","lineNumber":26,"sourceCode":"\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"tailscale.com/util/rands\"\n)\n\nconst (\n\tSelfUpdateIdentifier = \"self-update\"\n\tDatabasePostgres     = \"postgres\"\n\tDatabaseSqlite       = \"sqlite3\"\n)\n\n// Common errors.\nvar (\n\tErrCannotParsePrefix   = errors.New(\"cannot parse prefix\")\n\tErrInvalidAuthIDLength = errors.New(\"auth ID has invalid length\")\n\tErrInvalidAuthIDPrefix = errors.New(\"auth ID has invalid prefix\")\n)\n\nconst (\n\tauthIDPrefix       = \"hskey-authreq-\"\n\tauthIDRandomLength = 24\n\t// AuthIDLength is the total length of an AuthID: 14 (prefix) + 24 (random).\n\tAuthIDLength = 38\n)\n\ntype AuthID string\n\nfunc NewAuthID() (AuthID, error) {\n\treturn AuthID(authIDPrefix + rands.HexString(authIDRandomLength)), nil\n}\n\nfunc MustAuthID() AuthID {\n\trid, err := NewAuthID()","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/types/common.go#L8-L44","documentation":"ErrInvalidAuthIDLength is a sentinel error in hscontrol/types/common.go:26 returned by AuthID.Validate() (common.go:75-79) when an AuthID string's total length is not exactly AuthIDLength (38 = 14-char prefix 'hskey-authreq-' + 24 random chars). AuthIDFromString routes through Validate, so parsing any malformed-length ID fails with this error, wrapped with expected vs actual length.","triggerScenarios":"Calling types.AuthIDFromString on a truncated or padded auth request ID (e.g. 37 or 39 chars); hand-building an auth ID instead of using NewAuthID; IDs mangled by URL encoding/whitespace before parsing.","commonSituations":"Truncation of the registration URL parameter by proxies or clients; copy-paste of auth URLs losing characters; tests constructing IDs via string concatenation with wrong random-part length.","solutions":["Pass the auth ID through unmodified from registration URL to AuthIDFromString","Always generate IDs with types.NewAuthID() instead of constructing strings manually","Strip whitespace/newlines before parsing if IDs transit logs or terminals","Check for proxy/header truncation if IDs arrive consistently shortened"],"exampleFix":"// before\nid, err := types.AuthIDFromString(\"hskey-authreq-short\") // wrong length\n\n// after\nid, err := types.AuthIDFromString(rawURLParam) // pass the exact generated value","handlingStrategy":"validation","validationCode":"const authIDLen = 38\nif len(s) != authIDLen {\n    return errors.New(\"auth ID length invalid\")\n}","typeGuard":"func isValidAuthID(s string) bool {\n    return strings.HasPrefix(s, \"hskey-authreq-\") && len(s) == types.AuthIDLength\n}","tryCatchPattern":"if _, err := types.AuthIDFromString(s); err != nil {\n    if errors.Is(err, types.ErrInvalidAuthIDLength) { return 400 }\n}","preventionTips":["Pass auth IDs end-to-end without manual edits","Generate IDs only via types.NewAuthID()"],"tags":["headscale","auth","registration","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}