{"record":{"id":"1fae4c7b9cfda6a3","repo":"grafana/k6","slug":"invalid-tls-key-value-v-it-needs-to-be-a-pem","errorCode":null,"errorMessage":"invalid tls key value: '%#v', it needs to be a PEM formatted string","messagePattern":"invalid tls key value: '%#v', it needs to be a PEM formatted string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/params.go","lineNumber":235,"sourceCode":"\treturn result, nil\n}\n\nfunc parseConnectTLSParam(params *connectParams, v any) error {\n\tvar ok bool\n\tparams.TLS, ok = v.(map[string]any)\n\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid tls value: '%#v', expected (optional) keys: cert, key, password, and cacerts\", v)\n\t}\n\t// optional map keys below\n\tif cert, certok := params.TLS[\"cert\"]; certok {\n\t\tif _, ok = cert.(string); !ok {\n\t\t\treturn fmt.Errorf(\"invalid tls cert value: '%#v', it needs to be a PEM formatted string\", v)\n\t\t}\n\t}\n\tif key, keyok := params.TLS[\"key\"]; keyok {\n\t\tif _, ok = key.(string); !ok {\n\t\t\treturn fmt.Errorf(\"invalid tls key value: '%#v', it needs to be a PEM formatted string\", v)\n\t\t}\n\t}\n\tif pass, passok := params.TLS[\"password\"]; passok {\n\t\tif _, ok = pass.(string); !ok {\n\t\t\treturn fmt.Errorf(\"invalid tls password value: '%#v', it needs to be a string\", v)\n\t\t}\n\t}\n\tif cacerts, cacertsok := params.TLS[\"cacerts\"]; cacertsok {\n\t\tvar cacertsArray []any\n\t\tif cacertsArray, ok = cacerts.([]any); ok {\n\t\t\tfor _, cacertsArrayEntry := range cacertsArray {\n\t\t\t\tif _, ok = cacertsArrayEntry.(string); !ok {\n\t\t\t\t\treturn fmt.Errorf(\"invalid tls cacerts value: '%#v',\"+\n\t\t\t\t\t\t\" it needs to be a string or an array of PEM formatted strings\", v)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if _, ok = cacerts.(string); !ok {\n\t\t\treturn fmt.Errorf(\"invalid tls cacerts value: '%#v',\"+","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/params.go#L217-L253","documentation":"Thrown by k6's gRPC Client.connect() when the tls.key sub-option is present but not a string. parseConnectTLSParam (internal/js/modules/k6/grpc/params.go:233-236) requires the client private key to be a PEM-formatted string. As with cert, the message interpolates the whole tls map (v) rather than just the key value.","triggerScenarios":"tls: { key: 42 }, key: { ... }, key: [...], or any non-string value under tls.key. The type check is on the string type only; actual PEM parsing happens later during connection.","commonSituations":"Passing the key as an object or array; reading the key with a helper that returns an object; mismatching cert (string) and key (non-string) forms in the same tls object.","solutions":["Make tls.key a single PEM string containing '-----BEGIN ... PRIVATE KEY-----'.","Pair it with tls.cert — cert without key (or vice versa) will fail later at TLS setup even if both pass this check.","If the key is passphrase-protected, also provide tls.password as a string.","Check the tls.key entry's type with typeof in your script before connecting."],"exampleFix":"// before\nclient.connect('host:443', { tls: { cert: certPem, key: { pem: keyPem } } });\n\n// after\nclient.connect('host:443', { tls: { cert: certPem, key: keyPem } });","handlingStrategy":"validation","validationCode":"function validateTls(tls = {}) {\n  if ('key' in tls && typeof tls.key !== 'string') throw new Error('tls.key must be a PEM string');\n}","typeGuard":"const isPemString = (v) => typeof v === 'string' && /-----BEGIN [^-]+-----/.test(v);","tryCatchPattern":null,"preventionTips":["Always set cert and key together, both as strings.","If the key is encrypted, pair it with tls.password."],"tags":["grpc","tls","validation","client-cert"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}