{"record":{"id":"1bbf1f3e2cc068ab","repo":"grafana/k6","slug":"failed-to-append-ca-certificate-d-from-pem","errorCode":null,"errorMessage":"failed to append ca certificate [%d] from PEM","messagePattern":"failed to append ca certificate \\[(.+?)\\] from PEM","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/client.go","lineNumber":157,"sourceCode":"\t*/\n\tdecryptedKey, err := x509.DecryptPEMBlock(block, password) //nolint:staticcheck\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkey = pem.EncodeToMemory(&pem.Block{\n\t\tType:  blockType,\n\t\tBytes: decryptedKey,\n\t})\n\treturn key, nil\n}\n\nfunc buildTLSConfig(parentConfig *tls.Config, certificate, key []byte, caCertificates [][]byte) (*tls.Config, error) {\n\tvar cp *x509.CertPool\n\tif len(caCertificates) > 0 {\n\t\tcp, _ = x509.SystemCertPool()\n\t\tfor i, caCert := range caCertificates {\n\t\t\tif ok := cp.AppendCertsFromPEM(caCert); !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to append ca certificate [%d] from PEM\", i)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ignoring 'TLS MinVersion is too low' because this tls.Config will inherit MinValue and MaxValue\n\t// from the vu state tls.Config\n\n\ttlsCfg := &tls.Config{\n\t\tCipherSuites:       parentConfig.CipherSuites,\n\t\tInsecureSkipVerify: parentConfig.InsecureSkipVerify, //nolint:gosec\n\t\tMinVersion:         parentConfig.MinVersion,\n\t\tMaxVersion:         parentConfig.MaxVersion,\n\t\tRenegotiation:      parentConfig.Renegotiation,\n\t\tRootCAs:            cp,\n\t}\n\tif len(certificate) > 0 && len(key) > 0 {\n\t\tcert, err := tls.X509KeyPair(certificate, key)\n\t\tif err != nil {","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/client.go#L139-L175","documentation":"During grpc.connect() TLS setup, every entry of tls.cacerts (or the single-string form) is passed to x509.CertPool.AppendCertsFromPEM; when an entry yields no parsable CERTIFICATE block, buildTLSConfig fails with 'failed to append ca certificate [<index>] from PEM' (internal/js/modules/k6/grpc/client.go:157). The index is the position in the cacerts array, identifying exactly which entry is broken. Values must be the PEM content itself, never a file path.","triggerScenarios":"connect(addr, { tls: { cacerts: [open('ca.pem'), __ENV.EXTRA_CA] } }) where any entry is not valid PEM: a file path passed instead of contents, newlines stripped by env vars/CI secrets, cert+key concatenated into cacerts, or a mangled Base64 body.","commonSituations":"Loading CA material from environment variables or secret managers that collapse '\\n'; pasting single-line PEMs from browsers; mixing up the cacerts and cert/key options.","solutions":["Fix the entry indicated by the index: provide full PEM text including the -----BEGIN CERTIFICATE----- / -----END CERTIFICATE----- lines","Load PEMs from files with open() rather than env vars, or restore real newlines (str.split('\\\\n').join('\\n') style repair)","Validate each cert with a PEM-shape check before calling connect (see defense)"],"exampleFix":"// before (env var lost the newlines)\nconnect(addr, { tls: { cacerts: [__ENV.CA_PEM] } })\n\n// after (read the PEM file directly)\nconnect(addr, { tls: { cacerts: [open('ca.pem')] } })","handlingStrategy":"validation","validationCode":"const PEM_CERT = /-----BEGIN CERTIFICATE-----[\\s\\S]+-----END CERTIFICATE-----[\\s]*$/;\nfunction assertCaCerts(cacerts) {\n  const list = Array.isArray(cacerts) ? cacerts : [cacerts];\n  list.forEach((c, i) => {\n    if (!PEM_CERT.test(c)) throw new Error(`cacerts[${i}] is not a valid PEM certificate`);\n  });\n  return cacerts;\n}\nconnect(addr, { tls: { cacerts: assertCaCerts([open('ca.pem'), __ENV.EXTRA_CA]) } });","typeGuard":"function isPemCert(v) {\n  return typeof v === 'string' && /-----BEGIN CERTIFICATE-----/.test(v) && /-----END CERTIFICATE-----/.test(v);\n}","tryCatchPattern":"try { client.connect(addr, params); } catch (e) { if (/failed to append ca certificate \\[(\\d+)\\]/.test(e.message)) { /* fix that cacerts entry, reload from file, retry */ } throw e; }","preventionTips":["Load CA PEMs from files via open(), not env vars","If PEMs travel through env/CI secrets, re-expand escaped newlines before use","The index in the message points at the exact broken array entry"],"tags":["grpc","k6","tls","pem","certificates"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}