{"record":{"id":"34cc5bdb6676cbcf","repo":"juanfont/headscale","slug":"auth-id-has-invalid-prefix","errorCode":null,"errorMessage":"auth ID has invalid prefix","messagePattern":"auth ID has invalid prefix","errorType":"validation","errorClass":"ErrInvalidAuthIDPrefix","httpStatus":400,"severity":"error","filePath":"hscontrol/types/common.go","lineNumber":27,"sourceCode":"\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()\n\tif err != nil {","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/types/common.go#L9-L45","documentation":"ErrInvalidAuthIDPrefix is a sentinel error in hscontrol/types/common.go:27 returned by AuthID.Validate() (common.go:68-72) when an AuthID string does not start with the required 'hskey-authreq-' prefix (wrapped with the expected prefix in the message). Any parse via AuthIDOfString of a foreign or corrupted identifier hits it.","triggerScenarios":"Calling types.AuthIDFromString with arbitrary strings (node keys, session IDs, other prefixes); code passing the wrong URL parameter into the auth ID parser; refactors that switch which field is treated as the auth ID.","commonSituations":"Mixing up identifier types in registration callbacks; fuzzing or contract tests feeding generic strings; accepting auth IDs from untrusted input without prior format checks.","solutions":["Verify the value starts with 'hskey-authreq-' before/instead of parsing","Pass the exact auth ID issued during registration, not another identifier","Use AuthIDFromString and check errors.Is(err, ErrInvalidAuthIDPrefix) to reject early","Return 400 to clients sending malformed auth IDs rather than 500"],"exampleFix":"// before\nif !strings.HasPrefix(s, \"oidc-\") { ... }\n\n// after\nif _, err := types.AuthIDFromString(s); err != nil { return http.StatusBadRequest }","handlingStrategy":"type-guard","validationCode":"if !strings.HasPrefix(s, \"hskey-authreq-\") {\n    return errors.New(\"not an auth ID\")\n}","typeGuard":"func isAuthID(s string) bool {\n    return strings.HasPrefix(s, \"hskey-authreq-\")\n}","tryCatchPattern":"if errors.Is(err, types.ErrInvalidAuthIDPrefix) { /* reject input as 400, do not retry */ }","preventionTips":["Do not feed arbitrary identifiers into AuthIDFromString","Reject untrusted input failing the prefix check before any auth state lookup"],"tags":["headscale","auth","registration","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}