{"record":{"id":"503f0920fe767920","repo":"juanfont/headscale","slug":"failed-to-generate-api-key","errorCode":null,"errorMessage":"failed to generate API key","messagePattern":"failed to generate API key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/api_key.go","lineNumber":27,"sourceCode":"\t\"github.com/juanfont/headscale/hscontrol/types\"\n\t\"golang.org/x/crypto/bcrypt\"\n\t\"gorm.io/gorm\"\n\t\"tailscale.com/util/rands\"\n)\n\nconst (\n\tapiKeyPrefix       = \"hskey-api-\" //nolint:gosec // This is a prefix, not a credential\n\tapiKeyPrefixLength = 12\n\tapiKeyHashLength   = 64\n\n\t// Legacy format constants.\n\tlegacyAPIPrefixLength = 7\n\tlegacyAPIKeyLength    = 32\n)\n\nvar (\n\tErrAPIKeyFailedToParse    = errors.New(\"failed to parse ApiKey\")\n\tErrAPIKeyGenerationFailed = errors.New(\"failed to generate API key\")\n\tErrAPIKeyExpired          = errors.New(\"API key expired\")\n)\n\n// CreateAPIKey creates a new [types.APIKey] in a user, and returns it.\nfunc (hsdb *HSDatabase) CreateAPIKey(\n\texpiration *time.Time,\n) (string, *types.APIKey, error) {\n\t// Generate public prefix (12 chars)\n\tprefix := rands.HexString(apiKeyPrefixLength)\n\n\t// Generate secret (64 chars)\n\tsecret := rands.HexString(apiKeyHashLength)\n\n\t// Full key string (shown ONCE to user)\n\tkeyStr := apiKeyPrefix + prefix + \"-\" + secret\n\n\t// bcrypt hash of secret\n\thash, err := bcrypt.GenerateFromPassword([]byte(secret), bcrypt.DefaultCost)","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/api_key.go#L9-L45","documentation":"Sentinel in hscontrol/db/api_key.go returned by CreateAPIKey when generating the key material itself fails. CreateAPIKey builds a 12-char hex prefix and 64-char hex secret via tailscale.com/util/rands and hashes the secret; failure at that stage (essentially only a broken crypto/rand source) aborts creation before the row is persisted.","triggerScenarios":"Calling HSDatabase.CreateAPIKey(expiration) on a host where the system entropy source is unavailable or crypto/rand errors, causing prefix/secret generation to fail. Extremely rare in practice.","commonSituations":"Stripped-down containers with no /dev/urandom; exotic runtimes (some wasm/sandbox environments) where crypto/rand is not backed by a working entropy device.","solutions":["Verify /dev/urandom exists and is readable inside the container/host running headscale","Retry key creation once — transient entropy starvation is possible early after boot","If running in a restricted sandbox, expose a working entropy source to the process"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"key, ak, err := hsdb.CreateAPIKey(&exp)\nif err != nil {\n    if errors.Is(err, db.ErrAPIKeyGenerationFailed) {\n        // entropy-source failure: safe to retry once; otherwise surface it\n        key, ak, err = hsdb.CreateAPIKey(&exp)\n    }\n    if err != nil {\n        return fmt.Errorf(\"creating API key: %w\", err)\n    }\n}","preventionTips":["Ensure /dev/urandom is available in containers running headscale","Alert on key-creation failures — they indicate host-level entropy problems","Do not silently retry in a tight loop; one retry then fail fast"],"tags":["api-key","entropy","crypto-rand","headscale","go"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}