{"record":{"id":"836b18562321ff9e","repo":"JuliusBrussee/caveman","slug":"ca-bundle-w-config","errorCode":null,"errorMessage":"ca bundle: %w","messagePattern":"ca bundle: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/config/config.go","lineNumber":229,"sourceCode":"\t}\n\tfor _, name := range inheritedCABundleEnv {\n\t\tpath := strings.TrimSpace(env.String(name, \"\"))\n\t\tif path == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tloaded, err := cabundle.Certificates(path)\n\t\tif err != nil {\n\t\t\tc.SkippedCABundles = append(c.SkippedCABundles, SkippedCABundle{Env: name, Error: err.Error()})\n\t\t\tcontinue\n\t\t}\n\t\tcerts = append(certs, loaded...)\n\t}\n\tif len(certs) == 0 {\n\t\treturn nil\n\t}\n\tpool, err := cabundle.PoolOf(certs)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"ca bundle: %w\", err)\n\t}\n\tc.rootCAs = pool\n\treturn nil\n}\n\n// RootCAs returns the provider TLS trust store, or nil for Go's default.\nfunc (c Config) RootCAs() *x509.CertPool { return c.rootCAs }\n\n// UpstreamProxyFunc returns the Transport.Proxy selector for UpstreamProxy, or\n// nil for a direct client. Load parses UpstreamProxy once and rejects bad values\n// there, so for a loaded Config this is a cached-field accessor like RootCAs.\n// A Config built by hand (tests) never went through that gate, so it parses\n// here. An unparseable value dials direct rather than taking a request path\n// down with a panic: falling back to the environment default would both hide\n// the bad value and quietly move the SSRF boundary to a proxy the caller never\n// named.\nfunc (c Config) UpstreamProxyFunc() func(*http.Request) (*url.URL, error) {\n\tif c.upstreamProxyParsed {","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/internal/config/config.go#L211-L247","documentation":"After collecting certificates from ca_bundle and the inherited CA env vars, loadRootCAs calls cabundle.PoolOf to build an x509.CertPool (proxy/internal/config/config.go:229). If pooling fails — e.g. no usable certificates among the parsed ones — the error is wrapped as 'ca bundle: <err>' and startup fails. Distinct from the ca_bundle file-level error, this one concerns assembling the parsed certificates into a usable pool.","triggerScenarios":"ca_bundle or inherited SSL_CERT_FILE/REQUESTS_CA_BUNDLE/NODE_EXTRA_CA_CERTS files parse to zero usable certificates (e.g. a file containing only comments or keys), causing PoolOf to reject the empty/invalid certificate set.","commonSituations":"Bundle file containing only a private key; an empty placeholder file created by a bootstrap script; env var pointing at the wrong file format (e.g. PKCS#7 or DER instead of PEM certs).","solutions":["Ensure the bundle contains at least one valid PEM CERTIFICATE block","Re-export the corporate chain: openssl s_client -showcerts or your MITM tool's export function, saving full chain PEM","Check inherited env vars (SSL_CERT_FILE, REQUESTS_CA_BUNDLE, NODE_EXTRA_CA_CERTS) point at PEM certificate files","Convert non-PEM formats: openssl pkcs7 -print_certs -in file.p7b -out chain.pem"],"exampleFix":"// before (bundle.pem holds only a key)\n-----BEGIN PRIVATE KEY-----\n...\n// after (bundle.pem holds the chain)\n-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----","handlingStrategy":"validation","validationCode":"pemBytes, _ := os.ReadFile(bundlePath)\nblock, _ := pem.Decode(pemBytes)\nif block == nil || block.Type != \"CERTIFICATE\" {\n    return errors.New(\"bundle has no PEM CERTIFICATE blocks\")\n}","typeGuard":null,"tryCatchPattern":"if err := cfg.loadRootCAs(); err != nil {\n    if strings.Contains(err.Error(), \"ca bundle:\") {\n        logger.Error(\"CA bundle parsed but produced no usable certificates; export a full PEM chain\")\n    }\n    return err\n}","preventionTips":["Export full chains as PEM CERTIFICATE blocks, never private keys","Convert PKCS#7/DER bundles to PEM before use","Validate bundle contents at deploy time, not first request time"],"tags":["tls","ca-bundle","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"}