JuliusBrussee/caveman · error
githubapp: private key is not valid PEM
Error message
githubapp: private key is not valid PEM
What it means
Thrown by parseRSAPrivateKey in shared/platform/githubapp/githubapp.go:400 when pem.Decode finds no PEM block at all in the supplied private-key bytes. The function accepts PKCS#1 and PKCS#8 RSA keys (GitHub Apps download PKCS#1; KMS/openssl conversions often emit PKCS#8), but both require a valid PEM envelope first. This error means the input is not PEM in any form.
Source
Thrown at shared/platform/githubapp/githubapp.go:400
resp, err := a.httpClient.Do(req)
if err != nil {
return 0, nil, fmt.Errorf("githubapp: request failed: %w", err)
}
defer resp.Body.Close()
raw, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
if err != nil {
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 = 256View on GitHub (pinned to 27d5a3981a)
Solutions
- Ensure the value starts with -----BEGIN RSA PRIVATE KEY----- or -----BEGIN PRIVATE KEY----- and ends with the matching END line.
- Re-download the .pem from the GitHub App settings page and inject it verbatim.
- If the secret manager mangles newlines, store base64 of the file and decode in the container: base64 -d.
- Check for invisible characters (BOM, \r\n is fine, but smart quotes are not) by re-encoding and diffing.
Example fix
# before: JSON key stored in GITHUBAPP_PRIVATE_KEY
{"type":"service_account",...}
# after: the app's .pem file contents
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY----- Defensive patterns
Strategy: validation
Validate before calling
block, _ := pem.Decode(keyBytes)
if block == nil {
return errors.New("key is not PEM: expected BEGIN/END envelope")
} Type guard
func isPEMKey(b []byte) bool {
block, _ := pem.Decode(b)
return block != nil && strings.Contains(block.Type, "PRIVATE KEY")
} Try / catch
if err := githubapp.WithPrivateKey(keyBytes); err != nil {
return fmt.Errorf("github app credentials: %w", err) // surface at boot, not at first API call
} Prevention
- Inject the .pem downloaded from GitHub App settings verbatim.
- Store keys base64-encoded in secret managers and decode at mount time to survive newline mangling.
- Add a startup credential check that PEM-decodes the key before serving traffic.
When it happens
Trigger: The GitHub App private key env var / secret contains raw DER bytes, a JSON service-account key, base64 without PEM headers, a markdown-fenced key copied from docs, or an empty/LRM-character-polluted paste. pem.Decode returns a nil block and this error fires.
Common situations: Downloading the .pem from GitHub and processing it with xxd/base64 instead of using the file; storing the key in a secret manager field that stripped the BEGIN/END lines; copying the key through a chat/wiki that mangled dashes; picking the wrong file out of the app's key directory.
Related errors
- githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w
- githubapp: private key is not RSA
- %s (%s): certificate %d is unparseable, so the bundle is inc
- %s (%s): trailing PEM block is truncated after %d certificat
- %s (%s) contains no valid PEM certificate
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/03201b539879d6f8.
Report an issue: GitHub.