{"record":{"id":"db7774ac3829be87","repo":"JuliusBrussee/caveman","slug":"postgres-ca-certificate-configured-while-tls-is-d","errorCode":null,"errorMessage":"postgres: CA certificate configured while TLS is disabled","messagePattern":"postgres: CA certificate configured while TLS is disabled","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/postgresconfig/postgresconfig.go","lineNumber":60,"sourceCode":"\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}\n\treturn config, nil\n}\n","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/postgresconfig/postgresconfig.go#L42-L78","documentation":"ParsePoolConfig parsed a CA certificate (from the CA environment variable) and tried to install it as the TLS root pool, but the pgx connection config has TLSConfig == nil, meaning TLS is disabled for the connection. A CA is meaningless (and misleading) without TLS, so the library rejects the combination instead of ignoring the CA.","triggerScenarios":"Providing the CA certificate environment variable while the DATABASE_URL uses sslmode=disable or sslmode=allow/prefer in a way that leaves ConnConfig.TLSConfig nil; calling ParsePoolConfig with sslmode=disable plus a CA in non-production code paths that reach the CA handling block.","commonSituations":"Ops sets the CA env var in a deployment manifest but the DATABASE_URL still says sslmode=disable; local plaintext configs (TestLocalPlaintextRemainsAvailable) accidentally inheriting a CA secret from a shared environment.","solutions":["Set sslmode=verify-full in DATABASE_URL so pgx creates a TLSConfig to attach the CA to","Remove the CA certificate environment variable if the connection is intentionally plaintext (local dev only)","Check that pgxpool.ParseConfig did not strip TLS due to an earlier sslmode parsing issue in the URL"],"exampleFix":"// before\nDATABASE_URL=\"postgres://app:secret@db.internal:5432/prod?sslmode=disable\" PG_CA=/etc/certs/ca.pem\n// after\nDATABASE_URL=\"postgres://app:secret@db.internal:5432/prod?sslmode=verify-full\" PG_CA=/etc/certs/ca.pem","handlingStrategy":"validation","validationCode":"if caPEMSet && !hasVerifyFull(databaseURL) {\n    return fmt.Errorf(\"CA certificate requires sslmode=verify-full (TLS enabled)\")\n}","typeGuard":null,"tryCatchPattern":"cfg, err := postgresconfig.ParsePoolConfig(databaseURL)\nif err != nil && strings.Contains(err.Error(), \"CA certificate configured while TLS is disabled\") {\n    log.Fatal(\"sslmode=disable is incompatible with a configured CA; enable verify-full or drop the CA\")\n}","preventionTips":["Treat the CA secret and TLS-enabled sslmode as one unit in deployment templates","Remove CA env vars from local/plaintext dev environments","Document that the CA is only consumed when TLSConfig exists","Smoke-test ParsePoolConfig with the exact production env in CI"],"tags":["postgres","tls","configuration","certificate"],"backgroundTag":"conflicting-config-options","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"}