JuliusBrussee/caveman · error

ca_bundle

Error message

ca_bundle: %w

What it means

loadRootCAs (proxy/internal/config/config.go:208) loads the ca_bundle file via the shared cabundle parser to build a custom TLS trust store (for MITM/inspection setups). If reading or parsing the configured bundle fails, the underlying error is wrapped as 'ca_bundle: <err>' and startup aborts — fail-closed, since a broken trust store would silently break provider TLS.

Solutions

  1. Verify the file exists and is readable at the configured path (absolute path is safest)
  2. Ensure the file is a valid PEM certificate chain (certificates, not a private key)
  3. Test parsing: e.g. openssl x509 -in bundle.pem -noout to see if it is valid PEM
  4. If you do not need custom roots, clear ca_bundle and the inherited CA env vars

Example fix

// before (caveman.yaml)
ca_bundle: ./mitm.pem   # file missing
// after
ca_bundle: /etc/caveman/corp-mitm-chain.pem
Defensive patterns

Strategy: validation

Validate before calling

path := cfg.CABundle
if fi, err := os.Stat(path); err != nil || fi.IsDir() {
    return fmt.Errorf("ca_bundle %q is not a readable file", path)
}
if _, err := os.ReadFile(path); err != nil {
    return fmt.Errorf("ca_bundle unreadable: %w", err)
}

Try / catch

if err := cfg.loadRootCAs(); err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) {
        logger.Error("ca_bundle path problem", "path", pe.Path, "op", pe.Op)
    }
    return err
}

Prevention

When it happens

Trigger: ca_bundle in caveman.yaml (or CAVE_CA_BUNDLE) points to a nonexistent file, an unreadable path, a file with invalid PEM, or an empty/corrupt bundle.

Common situations: Corporate MITM proxy cert exported to the wrong path; PEM file with only a private key or truncated chain; wrong permissions after copying; path relative to the wrong working directory; env var pointing at a file deleted by a cleanup job.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/a10835fae4202471. Report an issue: GitHub.

Appendix: source

Thrown at proxy/internal/config/config.go:208

	if err := cfg.loadRootCAs(); err != nil {
		return Config{}, err
	}
	return cfg, nil
}

// inheritedCABundleEnv names the CA bundle variables other toolchains already
// read: Go/OpenSSL, Python requests, and Node (which Claude Code runs on).
var inheritedCABundleEnv = []string{"SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "NODE_EXTRA_CA_CERTS"}

// loadRootCAs builds the provider trust store. It stays nil — Go's default
// verification — when no bundle is configured, so the common case keeps the
// platform verifier untouched.
func (c *Config) loadRootCAs() error {
	var certs []*x509.Certificate
	if c.CABundle = strings.TrimSpace(c.CABundle); c.CABundle != "" {
		loaded, err := cabundle.Certificates(c.CABundle)
		if err != nil {
			return fmt.Errorf("ca_bundle: %w", err)
		}
		certs = append(certs, loaded...)
	}
	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
	}

View on GitHub (pinned to 3ee70a1026)