{"record":{"id":"9f7dc445c43bb7a0","repo":"caddyserver/caddy","slug":"source-module-is-not-a-ca-pool-provider","errorCode":null,"errorMessage":"source module is not a CA pool provider","messagePattern":"source module is not a CA pool provider","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/capools.go","lineNumber":867,"sourceCode":"// Provision implements caddy.Provisioner.\nfunc (ccp *CombinedCAPool) Provision(ctx caddy.Context) error {\n\tif len(ccp.SourcesRaw) == 0 {\n\t\treturn fmt.Errorf(\"no sources specified for combined CA pool\")\n\t}\n\n\t// Load all source modules\n\tsources, err := ctx.LoadModule(ccp, \"SourcesRaw\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"loading CA pool sources: %v\", err)\n\t}\n\n\tcaPool := x509.NewCertPool()\n\tvar allCerts []*x509.Certificate\n\n\tfor _, src := range sources.([]any) {\n\t\tca, ok := src.(CA)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"source module is not a CA pool provider\")\n\t\t}\n\t\tccp.sources = append(ccp.sources, ca)\n\n\t\tcertProvider, ok := ca.(CertificateProvider)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"source %T does not implement CertificateProvider (required for combining)\", ca)\n\t\t}\n\n\t\tcerts := certProvider.Certificates()\n\t\tif certs == nil {\n\t\t\treturn fmt.Errorf(\"source %T returned nil certificates\", ca)\n\t\t}\n\t\tfor _, cert := range certs {\n\t\t\tif cert == nil {\n\t\t\t\treturn fmt.Errorf(\"source %T returned a nil certificate\", ca)\n\t\t\t}\n\t\t\tcaPool.AddCert(cert)\n\t\t\tallCerts = append(allCerts, cert)","sourceCodeStart":849,"sourceCodeEnd":885,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/capools.go#L849-L885","documentation":"A module loaded as a source inside a combined CA pool did not type-assert to the CA interface (which requires CertPool() *x509.CertPool). Everything in the tls.ca_pool.source namespace should implement CA, so this fires when a non-CA module ends up in the sources list.","triggerScenarios":"Programmatically or via hand-written JSON placing a module that is not a CA pool source (e.g. a storage module or arbitrary module ID) into the combined pool's sources array. The Caddyfile adapter normally prevents this.","commonSituations":"Hand-crafted JSON configs; tooling that injects raw module objects; experimental modules that register under the wrong namespace.","solutions":["Use only modules whose ID starts with tls.ca_pool.source. in the combined pool's sources.","Prefer authoring via Caddyfile so the adapter only emits valid source modules.","If writing a custom pool module, ensure it implements CertPool() and is registered under the correct namespace."],"exampleFix":"// before (JSON)\n{\"source\": {\"module\": \"caddy.storage.file_system\"}}\n\n// after (JSON)\n{\"source\": {\"module\": \"tls.ca_pool.source.file\", \"pem_files\": [\"/etc/caddy/roots.pem\"]}}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// guard custom module wiring: only CA pool sources may be combined\nfunc isCAPoolSource(mod caddy.Module) bool {\n\t_, ok := mod.(caddytls.CA)\n\treturn ok\n}\n\n// stricter: also require CertificateProvider for combined pools\nfunc combinable(mod caddy.Module) bool {\n\tca, ok := mod.(caddytls.CA)\n\tif !ok {\n\t\treturn false\n\t}\n\t_, ok = ca.(caddytls.CertificateProvider)\n\treturn ok\n}","tryCatchPattern":null,"preventionTips":["Author trust pools via Caddyfile so the adapter only emits valid tls.ca_pool.source modules.","For JSON tooling, whitelist the tls.ca_pool.source namespace.","When writing custom pool modules, register under tls.ca_pool.source.<name> and implement CA."],"tags":["caddy","caddytls","type-assertion","module-system","combined-pool"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}