JuliusBrussee/caveman · critical
githubapp: private key is neither PKCS#1 nor PKCS#8 RSA
Error message
githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w
What it means
A PEM block was found, but neither x509.ParsePKCS1PrivateKey nor x509.ParsePKCS8PrivateKey could parse its DER payload. The key is PEM-armored yet its inner bytes are corrupt, truncated, or not a private key (e.g. a PUBLIC KEY or CERTIFICATE block).
Solutions
- Re-download the private key from GitHub App settings and use it unmodified.
- Validate locally: openssl rsa -in key.pem -check (or openssl pkey -in key.pem -text -noout); if OpenSSL rejects it, the file is corrupt.
- Confirm the header says RSA PRIVATE KEY or PRIVATE KEY, not PUBLIC KEY or CERTIFICATE.
- Regenerate via openssl genrsa / openssl pkcs8 -topk8 if you converted the key yourself.
Example fix
// before pem := "-----BEGIN PUBLIC KEY-----\n..." // wrong block type client, err := githubapp.New(pem) // after pem := "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----" client, err := githubapp.New(pem)
Defensive patterns
Strategy: validation
Validate before calling
func isParsableRSAPem(s string) bool {
block, _ := pem.Decode([]byte(s))
if block == nil {
return false
}
if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
return true
}
_, err := x509.ParsePKCS8PrivateKey(block.Bytes)
return err == nil
} Type guard
func isParsableRSAPem(s string) bool {
block, _ := pem.Decode([]byte(s))
if block == nil {
return false
}
_, err := x509.ParsePKCS8PrivateKey(block.Bytes)
return err == nil
} Try / catch
client, err := githubapp.New(pem)
if err != nil {
if strings.Contains(err.Error(), "neither PKCS#1 nor PKCS#8") {
return fmt.Errorf("private key payload corrupt or wrong type: %w", err)
}
return err
} Prevention
- Never hand-edit PEM files; re-download from GitHub App settings if in doubt.
- Verify with `openssl rsa -check -noout -in key.pem` before deploying.
- Ensure file transfers preserve content (no truncation or CRLF mangling).
- Confirm the block is a PRIVATE KEY, not PUBLIC KEY or CERTIFICATE.
When it happens
Trigger: New() given a PEM whose block bytes are corrupted or truncated (file cut off mid-copy), or a PEM containing a PUBLIC KEY / CERTIFICATE block rather than a private key.
Common situations: Manually editing the .pem and dropping characters; pasting the key through a tool that mangled line breaks or whitespace; accidentally using the app's public key or webhook cert; a bad self-converted export.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- githubapp: private key is not RSA
- githubapp: private key is not valid PEM
- githubapp: app id is required
- githubapp: base_url rejected by SSRF guard
- githubapp: decode installation
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/30bbf8e40af3287b.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/githubapp/githubapp.go:407
return resp.StatusCode, nil, fmt.Errorf("githubapp: read response: %w", err)
}
return resp.StatusCode, raw, nil
}
// parseRSAPrivateKey accepts a PKCS#1 ("RSA PRIVATE KEY") or PKCS#8
// ("PRIVATE KEY") PEM — GitHub Apps download PKCS#1, but Cloud KMS / openssl
// conversions emit PKCS#8, so we accept both.
func parseRSAPrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, fmt.Errorf("githubapp: private key is not valid PEM")
}
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
return key, nil
}
parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w", err)
}
key, ok := parsed.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("githubapp: private key is not RSA")
}
return key, nil
}
// snippet trims an error body so we never echo a large/secret-bearing response.
func snippet(b []byte) string {
const max = 256
s := strings.TrimSpace(string(b))
if len(s) > max {
return s[:max] + "…"
}
return s
}
View on GitHub (pinned to 3ee70a1026)