{"record":{"id":"1ff93304bdc8d52d","repo":"router-for-me/CLIProxyAPI","slug":"home-jwt-enrollment-secret-is-required","errorCode":null,"errorMessage":"home jwt enrollment_secret is required","messagePattern":"home jwt enrollment_secret is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/home/certificate.go","lineNumber":104,"sourceCode":"\t}\n\tpayload, errDecode := decodeJWTPart(parts[1])\n\tif errDecode != nil {\n\t\treturn claims, errDecode\n\t}\n\tif errUnmarshal := json.Unmarshal(payload, &claims); errUnmarshal != nil {\n\t\treturn claims, errUnmarshal\n\t}\n\tif strings.TrimSpace(claims.CertificateID) == \"\" {\n\t\treturn claims, fmt.Errorf(\"home jwt certificate_id is required\")\n\t}\n\tif strings.TrimSpace(claims.ClusterID) == \"\" {\n\t\treturn claims, fmt.Errorf(\"home jwt cluster_id is required\")\n\t}\n\tif normalizeFingerprint(claims.CAFingerprint) == \"\" {\n\t\treturn claims, fmt.Errorf(\"home jwt ca_fingerprint is required\")\n\t}\n\tif strings.TrimSpace(claims.EnrollmentSecret) == \"\" {\n\t\treturn claims, fmt.Errorf(\"home jwt enrollment_secret is required\")\n\t}\n\tif strings.TrimSpace(claims.IP) == \"\" || claims.Port <= 0 {\n\t\treturn claims, fmt.Errorf(\"home jwt target address is invalid\")\n\t}\n\treturn claims, nil\n}\n\nfunc decodeJWTPart(part string) ([]byte, error) {\n\tif decoded, errDecode := base64.RawURLEncoding.DecodeString(part); errDecode == nil {\n\t\treturn decoded, nil\n\t}\n\treturn base64.URLEncoding.DecodeString(part)\n}\n\nfunc defaultCertificatePaths() (certificatePaths, error) {\n\thomeDir, errHome := os.UserHomeDir()\n\tif errHome != nil {\n\t\treturn certificatePaths{}, errHome","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/home/certificate.go#L86-L122","documentation":"Claim-level validation in parseHomeJWTClaims: the enrollment_secret claim is empty or whitespace-only. The secret authenticates the client's certificate-signing request to the home coordinator during enrollment, so the token is unusable without it.","triggerScenarios":"The decoded JWT payload lacks enrollment_secret. Structural parsing and the earlier claim checks passed, so the token was minted incompletely.","commonSituations":"Token redaction (someone stripped the secret before sharing/logs), coordinator bug, or a token minted for a different flow that never includes the secret.","solutions":["Request an unredacted enrollment token from the coordinator","If the secret was removed for security when sharing, get a newly minted token instead of the sanitized copy","Verify the claim exists by base64url-decoding the payload before starting the server"],"exampleFix":"// before (JWT payload)\n{\"certificate_id\":\"c\",\"cluster_id\":\"k\",\"ca_fingerprint\":\"AA\",\"enrollment_secret\":\"\"}\n\n// after (JWT payload)\n{\"certificate_id\":\"c\",\"cluster_id\":\"k\",\"ca_fingerprint\":\"AA\",\"enrollment_secret\":\"s3cr3t\"}","handlingStrategy":"validation","validationCode":"// verify enrollment_secret claim is non-empty before use\npayload, _ := base64.RawURLEncoding.DecodeString(strings.Split(tok, \".\")[1])\nvar c map[string]any\n_ = json.Unmarshal(payload, &c)\nif s, ok := c[\"enrollment_secret\"].(string); !ok || strings.TrimSpace(s) == \"\" {\n    log.Fatal(\"enrollment token lacks enrollment_secret\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never use redacted/sanitized copies of enrollment tokens — request fresh ones","Keep token transfer channels lossless (secret manager or file, not chat-redacted logs)","Check all claims up front instead of discovering them missing one at a time"],"tags":["jwt","authentication","enrollment","home","secrets"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}