{"record":{"id":"71a0ea72abbd6a98","repo":"router-for-me/CLIProxyAPI","slug":"private-key-pem-decode-failed","errorCode":null,"errorMessage":"private_key pem decode failed","messagePattern":"private_key pem decode failed","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/auth/vertex/keyutil.go","lineNumber":76,"sourceCode":"\tpk := strings.ReplaceAll(raw, \"\\r\\n\", \"\\n\")\n\tpk = strings.ReplaceAll(pk, \"\\r\", \"\\n\")\n\tpk = stripANSIEscape(pk)\n\tpk = strings.ToValidUTF8(pk, \"\")\n\tpk = strings.TrimSpace(pk)\n\n\tnormalized := pk\n\tif block, _ := pem.Decode([]byte(pk)); block == nil {\n\t\t// Attempt to reconstruct from the textual payload.\n\t\tif reconstructed, err := rebuildPEM(pk); err == nil {\n\t\t\tnormalized = reconstructed\n\t\t} else {\n\t\t\treturn \"\", fmt.Errorf(\"private_key is not valid pem: %w\", err)\n\t\t}\n\t}\n\n\tblock, _ := pem.Decode([]byte(normalized))\n\tif block == nil {\n\t\treturn \"\", fmt.Errorf(\"private_key pem decode failed\")\n\t}\n\n\trsaBlock, err := ensureRSAPrivateKey(block)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(pem.EncodeToMemory(rsaBlock)), nil\n}\n\nfunc ensureRSAPrivateKey(block *pem.Block) (*pem.Block, error) {\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"pem block is nil\")\n\t}\n\n\tif block.Type == \"RSA PRIVATE KEY\" {\n\t\tif _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"private_key invalid rsa: %w\", err)\n\t\t}","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/vertex/keyutil.go#L58-L94","documentation":"Internal guard in sanitizePrivateKey: after either the original input or the reconstructed PEM from rebuildPEM, a second pem.Decode still returns a nil block (keyutil.go:73-76). Practically this indicates the reconstruction produced text that pem.Decode rejects (e.g. bad header formatting), so the key cannot be trusted.","triggerScenarios":"rebuildPEM succeeds in extracting base64 and re-encoding, but the resulting block still fails Go's strict pem.Decode (mismatched header spacing, BOM characters, non-UTF8 residue); extremely rare double-failure where the raw input almost decodes but has structural damage.","commonSituations":"Keys processed through multiple serialization layers (YAML on/off, JSON escapes, terminal paste with ANSI codes) leaving subtle corruption; editing the PEM headers by hand ('----- BEGIN' with a space).","solutions":["Discard the edited key and re-download the original JSON key file from GCP unchanged","Compare the PEM header/footer lines byte-for-byte with a known-good key (-----BEGIN PRIVATE KEY-----, no extra spaces)","Run the key through: cat sa.json | jq -r .private_key | openssl rsa -noout -check to validate independently of this code"],"exampleFix":"# before\n----- BEGIN PRIVATE KEY -----   # hand-edited spaces break pem.Decode\n# after\n-----BEGIN PRIVATE KEY-----","handlingStrategy":"validation","validationCode":"if block, _ := pem.Decode([]byte(pk)); block == nil {\n    return fmt.Errorf(\"private_key is not decodable PEM even after cleanup\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never hand-edit PEM header lines","Run openssl pkey -noout on keys during CI validation","Keep the GCP-downloaded file byte-identical end to end"],"tags":["vertex","pem","private-key","validation"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}