{"record":{"id":"c9d48c799d49271d","repo":"JuliusBrussee/caveman","slug":"githubapp-private-key-is-not-rsa","errorCode":null,"errorMessage":"githubapp: private key is not RSA","messagePattern":"githubapp: private key is not RSA","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"shared/platform/githubapp/githubapp.go","lineNumber":411,"sourceCode":"\n// parseRSAPrivateKey accepts a PKCS#1 (\"RSA PRIVATE KEY\") or PKCS#8\n// (\"PRIVATE KEY\") PEM — GitHub Apps download PKCS#1, but Cloud KMS / openssl\n// conversions emit PKCS#8, so we accept both.\nfunc parseRSAPrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {\n\tblock, _ := pem.Decode(pemBytes)\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key is not valid PEM\")\n\t}\n\tif key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {\n\t\treturn key, nil\n\t}\n\tparsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w\", err)\n\t}\n\tkey, ok := parsed.(*rsa.PrivateKey)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key is not RSA\")\n\t}\n\treturn key, nil\n}\n\n// snippet trims an error body so we never echo a large/secret-bearing response.\nfunc snippet(b []byte) string {\n\tconst max = 256\n\ts := strings.TrimSpace(string(b))\n\tif len(s) > max {\n\t\treturn s[:max] + \"…\"\n\t}\n\treturn s\n}\n","sourceCodeStart":393,"sourceCodeEnd":425,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/githubapp/githubapp.go#L393-L425","documentation":"Raised by parseRSAPrivateKey when the supplied PEM block decodes successfully but its DER payload is neither a PKCS#1 RSA key nor a PKCS#8 key. It fires after both x509.ParsePKCS1PrivateKey and x509.ParsePKCS8PrivateKey fail on block.Bytes, meaning the input is a well-formed PEM envelope around a non-RSA payload: typically an EC or Ed25519 key, a corrupted/truncated key body, or a certificate mistakenly passed where the GitHub App private key was expected. The input at fault is the private key bytes given to New (from the app's downloaded .pem or its KMS/openssl-converted equivalent), which must contain an RSA private key for GitHub App JWT signing.","triggerScenarios":"New() called with a PKCS#8 PEM containing an EC, Ed25519, or otherwise non-RSA private key.","commonSituations":"Generating a key with openssl genpkey -algorithm EC or ssh-keygen -t ed25519 and using it as a GitHub App key instead of the RSA key downloaded from GitHub; swapping in an unrelated non-RSA key from another service.","solutions":["Download the GitHub App's .pem from GitHub App settings — it is always RSA.","If generating your own key, use openssl genrsa 2048 and verify with openssl rsa -in key.pem -check.","Inspect the key header: 'BEGIN EC PRIVATE KEY' or EC-content PKCS#8 must be replaced by an RSA key."],"exampleFix":"// before\n$ openssl genpkey -algorithm ED25519 -out app.key  # non-RSA key\n// after\n$ openssl genrsa -out app.private-key.pem 2048     # RSA key for GitHub App","handlingStrategy":"validation","validationCode":"func isRSAKey(s string) bool {\n\tblock, _ := pem.Decode([]byte(s))\n\tif block == nil {\n\t\treturn false\n\t}\n\tif _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {\n\t\treturn true\n\t}\n\tparsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn false\n\t}\n\t_, ok := parsed.(*rsa.PrivateKey)\n\treturn ok\n}","typeGuard":"func isRSAKey(s string) bool {\n\tblock, _ := pem.Decode([]byte(s))\n\tif block == nil {\n\t\treturn false\n\t}\n\tparsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn false\n\t}\n\t_, ok := parsed.(*rsa.PrivateKey)\n\treturn ok\n}","tryCatchPattern":"client, err := githubapp.New(pem)\nif err != nil {\n\tif strings.Contains(err.Error(), \"is not RSA\") {\n\t\treturn errors.New(\"GitHub App keys must be RSA; re-download the .pem from app settings\")\n\t}\n\treturn err\n}","preventionTips":["Only use the .pem downloaded from the GitHub App settings page.","When generating keys, use openssl genrsa (RSA), never genpkey with EC/Ed25519.","Check the PEM header/algorithm before wiring a key into app config."],"tags":["github-app","rsa","x509","jwt"],"backgroundTag":"invalid-argument-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}