{"record":{"id":"7d9b5f8c0feac358","repo":"caddyserver/caddy","slug":"source-t-returned-nil-certificates","errorCode":null,"errorMessage":"source %T returned nil certificates","messagePattern":"source %T returned nil certificates","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/capools.go","lineNumber":878,"sourceCode":"\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)\n\t\t}\n\t}\n\n\tccp.pool = caPool\n\tccp.certs = allCerts\n\n\treturn nil\n}\n\n// Syntax:\n//","sourceCodeStart":860,"sourceCodeEnd":896,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/capools.go#L860-L896","documentation":"A source inside a combined CA pool implemented CertificateProvider but its Certificates() call returned a nil slice. The combined pool treats nil (as opposed to an empty non-nil slice) as a defective source and refuses to continue provisioning.","triggerScenarios":"A custom source module whose Certificates() returns nil because certificates were not stored during its Provision (e.g. field never populated on an error path, or value-receiver returning an unset field).","commonSituations":"Custom pool modules that lazily build the pool but forget to populate the certs slice; copy-paste module skeletons; receiver-type mistakes (value receiver on pointer-populated state).","solutions":["In the custom module, always initialize the certificates slice during Provision even when empty: `certs := []*x509.Certificate{}`.","Ensure Certificates() uses a pointer receiver over the same state that Provision populated.","Add a unit test asserting Certificates() is non-nil after Provision."],"exampleFix":"// before\nfunc (m *MyPool) Provision(ctx caddy.Context) error {\n\tm.pool = x509.NewCertPool()\n\treturn nil\n}\n\n// after\nfunc (m *MyPool) Provision(ctx caddy.Context) error {\n\tm.pool = x509.NewCertPool()\n\tm.certs = []*x509.Certificate{}\n\treturn nil\n}","handlingStrategy":"validation","validationCode":"// custom modules: always initialize the certs slice in Provision\nfunc (m *MyPool) Provision(ctx caddy.Context) error {\n\tm.pool = x509.NewCertPool()\n\tm.certs = []*x509.Certificate{} // non-nil, even when empty\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Unit-test Certificates() for non-nil after Provision in custom pool modules.","Use pointer receivers consistently so Provision's writes are visible to Certificates().","Return an empty non-nil slice rather than nil on no-certificate paths."],"tags":["caddy","caddytls","nil-check","plugin","combined-pool"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}