{"record":{"id":"37a1617827905d6b","repo":"router-for-me/CLIProxyAPI","slug":"home-jwt-is-invalid","errorCode":null,"errorMessage":"home jwt is invalid","messagePattern":"home jwt is invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/home/certificate.go","lineNumber":85,"sourceCode":"\t\tEnabled: true,\n\t\tNodeID:  strings.TrimSpace(claims.CertificateID),\n\t\tHost:    strings.TrimSpace(claims.IP),\n\t\tPort:    claims.Port,\n\t\tTLS: config.HomeTLSConfig{\n\t\t\tEnable:              true,\n\t\t\tCACert:              paths.CACert,\n\t\t\tClientCert:          paths.ClientCert,\n\t\t\tClientKey:           paths.ClientKey,\n\t\t\tUseTargetServerName: true,\n\t\t},\n\t}, nil\n}\n\nfunc parseHomeJWTClaims(rawJWT string) (homeJWTClaims, error) {\n\tvar claims homeJWTClaims\n\tparts := strings.Split(strings.TrimSpace(rawJWT), \".\")\n\tif len(parts) != 3 {\n\t\treturn claims, fmt.Errorf(\"home jwt is invalid\")\n\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) == \"\" {","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/home/certificate.go#L67-L103","documentation":"Thrown by parseHomeJWTClaims in internal/home/certificate.go when the home enrollment JWT does not have exactly three dot-separated segments (header.payload.signature). It is a shape check performed before any decoding — the token is not a structurally valid JWT.","triggerScenarios":"Passing a value that is not a compact JWS: a raw opaque token, a truncated/copied-incomplete string, a JWE (5 parts), or an empty/whitespace string with extra dots.","commonSituations":"Copy-pasting the enrollment token and losing the tail, environment-variable wrapping/truncation, passing an API key instead of the enrollment JWT, or shell quoting that splits the token.","solutions":["Re-copy the full enrollment JWT; it must look like xxxxx.yyyyy.zzzzz with exactly two dots","Verify the variable/argument carrying it was not truncated (echo ${#TOKEN} and compare with the source)","Confirm you are passing the home enrollment token, not an API key or session token"],"exampleFix":"# before\nCLI_PROXY_HOME_TOKEN=\"eyJhbGciOiJFUzI1NiIsImtpZCI6\"   # truncated\n\n# after\nCLI_PROXY_HOME_TOKEN=\"eyJhbGciOiJFUzI1NiIs...J9.eyJjZXJ0aWZpY2F0ZV9pZCI6...In0.SIG\"","handlingStrategy":"validation","validationCode":"func looksLikeCompactJWT(s string) bool {\n    parts := strings.Split(strings.TrimSpace(s), \".\")\n    return len(parts) == 3 && parts[0] != \"\" && parts[1] != \"\" && parts[2] != \"\"\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check the token has exactly two dots before passing it to enrollment","Store enrollment tokens in files or env vars verbatim; avoid shell line-wrapping","Log token length (never content) when ingest fails to spot truncation"],"tags":["jwt","authentication","enrollment","home","validation"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}