{"record":{"id":"90b71aac13b01b21","repo":"router-for-me/CLIProxyAPI","slug":"missing-pem-markers","errorCode":null,"errorMessage":"missing pem markers","messagePattern":"missing pem markers","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/auth/vertex/keyutil.go","lineNumber":135,"sourceCode":"\t\tif rsaKey, ok := key.(*rsa.PrivateKey); ok {\n\t\t\tder := x509.MarshalPKCS1PrivateKey(rsaKey)\n\t\t\treturn &pem.Block{Type: \"RSA PRIVATE KEY\", Bytes: der}, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"private_key uses unsupported format\")\n}\n\nfunc rebuildPEM(raw string) (string, error) {\n\tkind := \"PRIVATE KEY\"\n\tif strings.Contains(raw, \"RSA PRIVATE KEY\") {\n\t\tkind = \"RSA PRIVATE KEY\"\n\t}\n\theader := \"-----BEGIN \" + kind + \"-----\"\n\tfooter := \"-----END \" + kind + \"-----\"\n\tstart := strings.Index(raw, header)\n\tend := strings.Index(raw, footer)\n\tif start < 0 || end <= start {\n\t\treturn \"\", fmt.Errorf(\"missing pem markers\")\n\t}\n\tbody := raw[start+len(header) : end]\n\tpayload := filterBase64(body)\n\tif payload == \"\" {\n\t\treturn \"\", fmt.Errorf(\"private_key base64 payload empty\")\n\t}\n\tder, err := base64.StdEncoding.DecodeString(payload)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"private_key base64 decode failed: %w\", err)\n\t}\n\tblock := &pem.Block{Type: kind, Bytes: der}\n\treturn string(pem.EncodeToMemory(block)), nil\n}\n\nfunc filterBase64(s string) string {\n\tvar b strings.Builder\n\tfor _, r := range s {\n\t\tswitch {","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/vertex/keyutil.go#L117-L153","documentation":"From rebuildPEM, the recovery path that tries to reconstruct a PEM from damaged textual input: it searches for '-----BEGIN <KIND>-----' and '-----END <KIND>-----' and fails because one or both markers are absent, or END appears before BEGIN (keyutil.go:130-136). The kind is 'RSA PRIVATE KEY' only if that string appears, otherwise 'PRIVATE KEY'.","triggerScenarios":"Key pasted without the BEGIN/END lines; footer missing because of truncation; only the base64 body retained; markers present for the other kind (e.g. 'RSA PRIVATE KEY' body but code looked for plain 'PRIVATE KEY' first and vice versa when only one flavor exists).","commonSituations":"Secret copied out of a JSON viewer that hid the header lines; CI truncating long env vars; users pasting only the 'middle' of the key assuming the rest is implied.","solutions":["Restore the full PEM including BEGIN/END lines from the original GCP JSON file","If reconstructing manually, wrap the base64 body: -----BEGIN PRIVATE KEY----- / body / -----END PRIVATE KEY----- with real newlines","Re-download the service account key rather than hand-repairing it"],"exampleFix":"# before (markers lost)\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASC...\n# after\n-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASC...\n-----END PRIVATE KEY-----","handlingStrategy":"validation","validationCode":"if !strings.Contains(pk, \"-----BEGIN\") || !strings.Contains(pk, \"-----END\") {\n    return fmt.Errorf(\"PEM markers missing from private_key\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always copy the complete PEM including BEGIN/END lines","Use file transfer for keys instead of terminal paste","Add a lint step that greps for markers in stored credentials"],"tags":["vertex","pem","truncation","private-key"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}