{"record":{"id":"76a86e2e707ec2ea","repo":"JuliusBrussee/caveman","slug":"postgres-s-contains-no-valid-certificate","errorCode":null,"errorMessage":"postgres: %s contains no valid certificate","messagePattern":"postgres: (.+?) contains no valid certificate","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/postgresconfig/postgresconfig.go","lineNumber":57,"sourceCode":"\t}\n\n\tconfig, err := pgxpool.ParseConfig(databaseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"postgres: parse DATABASE_URL: %w\", err)\n\t}\n\tcaPEM, err := caPEMFromEnvironment()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif production && caPEM == \"\" {\n\t\treturn nil, fmt.Errorf(\"postgres: %s or %s is required in production\", caEnvironment, caFileEnvironment)\n\t}\n\tif caPEM == \"\" {\n\t\treturn config, nil\n\t}\n\troots := x509.NewCertPool()\n\tif !roots.AppendCertsFromPEM([]byte(caPEM)) {\n\t\treturn nil, fmt.Errorf(\"postgres: %s contains no valid certificate\", caEnvironment)\n\t}\n\tif config.ConnConfig.TLSConfig == nil {\n\t\treturn nil, errors.New(\"postgres: CA certificate configured while TLS is disabled\")\n\t}\n\tconfig.ConnConfig.TLSConfig.RootCAs = roots\n\tconfig.ConnConfig.TLSConfig.InsecureSkipVerify = false\n\tconfig.ConnConfig.TLSConfig.ServerName = config.ConnConfig.Host\n\tfor _, fallback := range config.ConnConfig.Fallbacks {\n\t\tif fallback.TLSConfig == nil {\n\t\t\tif production {\n\t\t\t\treturn nil, errors.New(\"postgres: production connection includes a plaintext fallback\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tfallback.TLSConfig.RootCAs = roots\n\t\tfallback.TLSConfig.InsecureSkipVerify = false\n\t\tfallback.TLSConfig.ServerName = fallback.Host\n\t}","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/postgresconfig/postgresconfig.go#L39-L75","documentation":"ParsePoolConfig validates the CA certificate PEM (taken from the CA env var or file) by appending it to an x509 cert pool. If AppendCertsFromPEM fails, the supplied data contains no parseable certificate, so the library refuses to build a hardened TLS pool. This prevents silently starting a pool without the intended CA trust anchor.","triggerScenarios":"Calling ParsePoolConfig (directly or via NewPool) with the CA environment variable set to a non-empty value that x509.CertPool.AppendCertsFromPEM cannot parse — e.g. garbage text, a raw public key, a private key, truncated PEM, or DER/binary bytes.","commonSituations":"CA env var set to the certificate path instead of PEM contents (the file variant should be used instead); pasting only part of a certificate; copying a base64 body without BEGIN/END lines; pointing the env var at a key file rather than a cert.","solutions":["Ensure the CA env var contains full PEM text including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines, or use the CA file env var pointing to a regular .pem/.crt file.","Verify the file contents with `openssl x509 -in ca.crt -text -noout` to confirm it is a valid X.509 certificate.","If you do not need custom TLS CA verification, unset the CA environment variable entirely so the pool uses system roots.","Check for truncation or encoding corruption (e.g. YAML/base64 wrapping, CRLF mangling) when injecting the PEM via secret managers."],"exampleFix":"// before\nexport POSTGRES_CA=\"-----BEGIN PUBLIC KEY-----...\" // not a certificate\n// after\nexport POSTGRES_CA_FILE=/etc/ssl/certs/ca.crt // or POSTGRES_CA=$(cat ca.crt)","handlingStrategy":"validation","validationCode":"func validPEM(pemStr string) bool {\n\treturn strings.Contains(pemStr, \"-----BEGIN CERTIFICATE-----\") &&\n\t\tx509.NewCertPool().AppendCertsFromPEM([]byte(pemStr))\n}","typeGuard":null,"tryCatchPattern":"config, err := postgresconfig.ParsePoolConfig(databaseURL)\nif err != nil {\n\tvar pe *os.PathError\n\tif errors.As(err, &pe) { /* env var misconfigured */ }\n\treturn fmt.Errorf(\"pool config: %w\", err)\n}","preventionTips":["Store CA certs as files and use the file-path env var instead of inline PEM.","Validate PEM content at deploy time with openssl x509.","Add a startup smoke test that calls ParsePoolConfig against the real environment."],"tags":["postgres","tls","certificate","config"],"backgroundTag":"invalid-config-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}