{"record":{"id":"7037bb535ee62972","repo":"router-for-me/CLIProxyAPI","slug":"private-key-is-not-valid-pem-w","errorCode":null,"errorMessage":"private_key is not valid pem: %w","messagePattern":"private_key is not valid pem: %w","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/auth/vertex/keyutil.go","lineNumber":70,"sourceCode":"\t}\n\tclone[\"private_key\"] = normalized\n\treturn clone, nil\n}\n\nfunc sanitizePrivateKey(raw string) (string, error) {\n\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\")","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/vertex/keyutil.go#L52-L88","documentation":"Wrapped error from sanitizePrivateKey when the private_key content is not decodable as PEM and the recovery path rebuildPEM also failed (keyutil.go:63-70). The sanitizer normalizes line endings, strips ANSI escapes, and forces valid UTF-8 before attempting pem.Decode; when both direct decode and textual reconstruction fail, the key is unusable.","triggerScenarios":"private_key containing base64 garbage instead of a PEM block; a key truncated mid-copy so footer '-----END ... KEY-----' is missing; binary corruption; the key embedded with real literal '\\n' escapes that still cannot be reconstructed; rebuildPEM failing because markers are absent or reversed.","commonSituations":"Copy-pasting keys through chat/email that mangles newlines; storing the key in YAML/env where '\\n' escaping behaves unexpectedly; keys truncated by shell heredocs or column limits in secrets managers.","solutions":["Regenerate the service account key in GCP and re-download the JSON file wholesale rather than editing the key string","Inspect the wrapped %w cause: 'missing pem markers' means BEGIN/END lines are gone; 'base64 decode failed' means the body is corrupt","If storing via env var or secret manager, confirm newlines survive round-trip (compare sha256 of the decoded value with the original file)"],"exampleFix":"# before (key mangled into one line, markers lost)\nexport SA_KEY=\"MIIEvQIBADANBg...\"\n# after: keep the full PEM text with real newlines\nexport SA_KEY=\"-----BEGIN RSA PRIVATE KEY-----\\nMIIEvQ...\\n-----END RSA PRIVATE KEY-----\\n\"  # \\n interpreted by the consumer","handlingStrategy":"validation","validationCode":"pk := sa[\"private_key\"].(string)\nif !strings.Contains(pk, \"-----BEGIN\") || !strings.Contains(pk, \"-----END\") {\n    return fmt.Errorf(\"private_key lost PEM markers\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Transport keys as whole files, not pasted strings","Verify sha256 of key files after any transfer","Prefer re-downloading from GCP over repairing mangled keys"],"tags":["vertex","pem","private-key","corruption"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}