JuliusBrussee/caveman · error
ca bundle
Error message
ca bundle: %w
What it means
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.
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
Example fix
// before (bundle.pem holds only a key) -----BEGIN PRIVATE KEY----- ... // after (bundle.pem holds the chain) -----BEGIN CERTIFICATE----- MIID... -----END CERTIFICATE-----
Defensive patterns
Strategy: validation
Validate before calling
pemBytes, _ := os.ReadFile(bundlePath)
block, _ := pem.Decode(pemBytes)
if block == nil || block.Type != "CERTIFICATE" {
return errors.New("bundle has no PEM CERTIFICATE blocks")
} Try / catch
if err := cfg.loadRootCAs(); err != nil {
if strings.Contains(err.Error(), "ca bundle:") {
logger.Error("CA bundle parsed but produced no usable certificates; export a full PEM chain")
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- ca_bundle
- postgres: contains no valid certificate
- %s: %w
- postgres: CA certificate configured while TLS is disabled
- postgres: must point to a regular file
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/836b18562321ff9e.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/internal/config/config.go:229
}
for _, name := range inheritedCABundleEnv {
path := strings.TrimSpace(env.String(name, ""))
if path == "" {
continue
}
loaded, err := cabundle.Certificates(path)
if err != nil {
c.SkippedCABundles = append(c.SkippedCABundles, SkippedCABundle{Env: name, Error: err.Error()})
continue
}
certs = append(certs, loaded...)
}
if len(certs) == 0 {
return nil
}
pool, err := cabundle.PoolOf(certs)
if err != nil {
return fmt.Errorf("ca bundle: %w", err)
}
c.rootCAs = pool
return nil
}
// RootCAs returns the provider TLS trust store, or nil for Go's default.
func (c Config) RootCAs() *x509.CertPool { return c.rootCAs }
// UpstreamProxyFunc returns the Transport.Proxy selector for UpstreamProxy, or
// nil for a direct client. Load parses UpstreamProxy once and rejects bad values
// there, so for a loaded Config this is a cached-field accessor like RootCAs.
// A Config built by hand (tests) never went through that gate, so it parses
// here. An unparseable value dials direct rather than taking a request path
// down with a panic: falling back to the environment default would both hide
// the bad value and quietly move the SSRF boundary to a proxy the caller never
// named.
func (c Config) UpstreamProxyFunc() func(*http.Request) (*url.URL, error) {
if c.upstreamProxyParsed {View on GitHub (pinned to 3ee70a1026)