CVE-2026-62994: Github.com/coredns/coredns CoreDNS `k8s_external` Headless AXFR Can Vulnerability
LOWCVE-2026-62994 is a LOW (CVSS 3.7) coredns `k8s_external` headless axfr can vulnerability (CWE-755: Improper Handling of Exceptional Conditions) affecting Github.com/coredns/coredns libraries in the Go ecosystem. The issue involves coredns `k8s_external` headless axfr can emit an empty transfer batch that panics the `transfer` plugin. Remediation is available by upgrading affected packages to version 1.14.5.
CVE-2026-62994 at a Glance
Security Impact
CVSS: 3.7
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L).
How to Fix CVE-2026-62994
To remediate this vulnerability, upgrade all affected Github.com/coredns/coredns dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/coredns/coredns to 1.14.5
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/coredns/coredns
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-62994, inspect your project dependencies for Github.com/coredns/coredns:
inspect dependencies for github.com/coredns/coredns
Analyze the terminal output to locate any version of Github.com/coredns/coredns in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.14.5), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-755: Improper Handling of Exceptional Conditions, where the product does not handle or incorrectly handles an exceptional condition. In Github.com/coredns/coredns, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-62994 Affected Github.com/coredns/coredns Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/coredns/coredns
|
Go |
≥ 1.9.4
< 1.14.5
|
1.14.5
|
References
Frequently Asked Questions
What is CVE-2026-62994?
Which Github.com/coredns/coredns versions are affected?
What version fixes CVE-2026-62994?
Is github.com/coredns/coredns affected?
What is the CVSS score of CVE-2026-62994?
What CWE is associated with CVE-2026-62994?
How can I check Go dependencies for CVE-2026-62994?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-74w3-63xv-x9mv",
"modified" : "2026-10-16T19:26:42Z",
"published" : "2026-10-16T19:26:42Z",
"aliases" : [ "CVE-2026-62994" ],
"summary" : "CoreDNS `k8s_external` headless AXFR can emit an empty transfer batch that panics the `transfer` plugin",
"details" : "### Summary\n\nA network DNS client allowed to request AXFR for a CoreDNS zone can trigger a panic when CoreDNS is configured with `k8s_external` headless-service zone transfers and Kubernetes contains a headless service endpoint with no declared ports. The built-in `k8s_external` transferer can emit an empty `[]dns.RR` batch, and the shared `transfer` plugin indexes `records[0]` without checking that the batch is non-empty. In normal CoreDNS mode the panic is recovered and returned as `SERVFAIL`, but repeated triggering can disrupt requests and amplify panic logging; if the `debug` plugin disables recovery, the same path can crash the CoreDNS process.\n\n### Details\n\n`plugin/transfer/transfer.go` assumes that every `Transferer` emits only non-empty `[]dns.RR` batches. During AXFR/IXFR response generation it indexes `records[0]` without checking the slice length:\n\n- `plugin/transfer/transfer.go:129-131`\n\n```go\nfor records := range pchan {\n if x, ok := records[0].(*dns.SOA); ok && soa == nil {\n soa = x\n }\n```\n\nThe built-in `k8s_external` transferer violates that contract for headless services whose endpoints have no declared ports. `plugin/k8s_external/transfer.go` sends the SRV result slice unconditionally:\n\n- `plugin/k8s_external/transfer.go:104-106`\n\n```go\nrecs, _ := e.srv(ctx, svcs, s)\nch <- recs\n```\n\n`plugin/k8s_external/msg_to_dns.go` intentionally skips services whose port is `-1`:\n\n- `plugin/k8s_external/msg_to_dns.go:107-112`\n\n```go\nfor _, s := range services {\n // Don't add the entry if the port is -1 (invalid). The kubernetes plugin uses port -1 when a service/endpoint\n // does not have any declared ports.\n if s.Port == -1 {\n continue\n }\n```\n\nThe Kubernetes EndpointSlice conversion creates that sentinel when an EndpointSlice has no ports:\n\n- `plugin/kubernetes/object/endpoint.go:62-66`\n\n```go\nif len(ends.Ports) == 0 {\n // Add sentinel if there are no ports.\n e.Subsets[0].Ports = []EndpointPort{{Port: -1}}\n```\n\nThis creates a built-in source-to-sink chain:\n\n1. Kubernetes EndpointSlice has ready addresses and no ports.\n2. CoreDNS `kubernetes` converts it to an endpoint with `Port: -1`.\n3. `k8s_external` with `headless` includes that service in transfer data.\n4. `k8s_external.Transfer` sends an empty SRV `[]dns.RR` batch.\n5. `transfer.ServeDNS` indexes `records[0]` and panics.\n\nSuggested remediation:\n\n1. In `plugin/transfer/transfer.go`, skip or reject empty `records` batches before indexing `records[0]`.\n2. In `plugin/k8s_external/transfer.go`, only send `recs` when `len(recs) > 0` in the headless endpoint and port/protocol paths.\n3. Add regression tests covering `Port: -1` headless services and empty transfer batches.\n\nExample consumer-side guard:\n\n```go\nfor records := range pchan {\n if len(records) == 0 {\n continue\n }\n if x, ok := records[0].(*dns.SOA); ok && soa == nil {\n soa = x\n }\n ...\n}\n```\n\n### PoC\n\nSafe local unit-level reproduction was performed. It does not contact external infrastructure and does not require a live Kubernetes cluster.\n\n#### Reproducer A: the `transfer` sink panics on an empty batch\n\nA temporary package test was added under `plugin/transfer`:\n\n```go\npackage transfer\n\nimport (\n \"context\"\n \"fmt\"\n \"testing\"\n\n \"github.com/coredns/coredns/plugin/pkg/dnstest\"\n \"github.com/coredns/coredns/plugin/test\"\n\n \"github.com/miekg/dns\"\n)\n\ntype auditEmptyBatchTransferer struct{}\n\nfunc (auditEmptyBatchTransferer) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) {\n if zone != \"example.org.\" {\n return nil, ErrNotAuthoritative\n }\n ch := make(chan []dns.RR, 3)\n ch <- []dns.RR{test.SOA(fmt.Sprintf(\"%s 100 IN SOA ns.dns.%s hostmaster.%s %d 7200 1800 86400 100\", zone, zone, zone, 1))}\n ch <- []dns.RR{}\n close(ch)\n return ch, nil\n}\n\nfunc TestAuditEmptyTransferBatchPanics(t *testing.T) {\n tr := &Transfer{\n Transferers: []Transferer{auditEmptyBatchTransferer{}},\n xfrs: []*xfr{{Zones: []string{\"example.org.\"}, to: []string{\"*\"}}},\n }\n\n req := new(dns.Msg)\n req.SetQuestion(\"example.org.\", dns.TypeAXFR)\n rec := dnstest.NewRecorder(&test.ResponseWriter{TCP: true})\n\n defer func() {\n if rec := recover(); rec == nil {\n t.Fatal(\"expected empty transfer batch to panic\")\n }\n }()\n\n _, _ = tr.ServeDNS(context.Background(), rec, req)\n}\n```\n\nVerification command and result:\n\n```text\n$ go test ./plugin/transfer -run TestAuditEmptyTransferBatchPanics -count=1 -v\n=== RUN TestAuditEmptyTransferBatchPanics\n--- PASS: TestAuditEmptyTransferBatchPanics (0.00s)\nPASS\nok \tgithub.com/coredns/coredns/plugin/transfer\t0.005s\n```\n\n#### Reproducer B: the built-in `k8s_external` transferer emits the empty batch\n\nA temporary package test was added under `plugin/k8s_external`:\n\n```go\npackage external\n\nimport (\n \"testing\"\n \"time\"\n\n \"github.com/coredns/coredns/plugin/etcd/msg\"\n \"github.com/coredns/coredns/plugin/kubernetes\"\n \"github.com/coredns/coredns/request\"\n\n \"github.com/miekg/dns\"\n)\n\nfunc TestAuditHeadlessNoPortTransferEmitsEmptyBatch(t *testing.T) {\n e := New()\n e.Zones = []string{\"example.com.\"}\n e.headless = true\n e.externalAddrFunc = func(request.Request, bool) []dns.RR { return nil }\n e.externalSerialFunc = func(string) uint32 { return 1 }\n e.externalServicesFunc = func(string, bool) ([]msg.Service, map[string][]msg.Service) {\n return nil, map[string][]msg.Service{\n \"/coredns/com/example/testns/headless/\" + kubernetes.Endpoint: {{Host: \"192.0.2.10\", Port: -1, TTL: 5, Key: \"/coredns/com/example/testns/headless/endpoint-a\"}},\n }\n }\n\n ch, err := e.Transfer(\"example.com.\", 0)\n if err != nil {\n t.Fatalf(\"Transfer returned error: %v\", err)\n }\n\n deadline := time.After(time.Second)\n for {\n select {\n case batch, ok := <-ch:\n if !ok {\n t.Fatal(\"transfer closed before emitting an empty batch\")\n }\n if len(batch) == 0 {\n return\n }\n case <-deadline:\n t.Fatal(\"timed out waiting for an empty batch\")\n }\n }\n}\n```\n\nVerification command and result:\n\n```text\n$ go test ./plugin/k8s_external -run TestAuditHeadlessNoPortTransferEmitsEmptyBatch -count=1 -v\n=== RUN TestAuditHeadlessNoPortTransferEmitsEmptyBatch\n--- PASS: TestAuditHeadlessNoPortTransferEmitsEmptyBatch (0.00s)\nPASS\nok \tgithub.com/coredns/coredns/plugin/k8s_external\t0.012s\n```\n\nAdditional focused verification:\n\n```text\n$ go test ./plugin/transfer ./plugin/k8s_external ./plugin/kubernetes -run 'TestAudit|^$' -count=1 -v\nok \tgithub.com/coredns/coredns/plugin/transfer\t0.006s\nok \tgithub.com/coredns/coredns/plugin/k8s_external\t0.013s\nok \tgithub.com/coredns/coredns/plugin/kubernetes\t0.116s\n```\n\nAffected package suites:\n\n```text\n$ go test ./plugin/transfer ./plugin/k8s_external ./plugin/kubernetes -count=1\nok \tgithub.com/coredns/coredns/plugin/transfer\t0.010s\nok \tgithub.com/coredns/coredns/plugin/k8s_external\t0.011s\nok \tgithub.com/coredns/coredns/plugin/kubernetes\t0.194s\n```\n\n### Impact\n\nA network client allowed to request AXFR for the affected zone can trigger a panic in CoreDNS when all prerequisites are present.\n\nRequired configuration/state:\n\n```corefile\nexample.com:53 {\n kubernetes cluster.local\n k8s_external example.com {\n headless\n }\n transfer example.com {\n to *\n }\n}\n```\n\nAdditional prerequisite:\n\n- The Kubernetes API state contains a headless-service endpoint represented by an EndpointSlice with ready addresses and no ports, producing CoreDNS `msg.Service{Port: -1}`.\n\nIn normal CoreDNS mode, `core/dnsserver/server.go:267-279` recovers the panic and returns `SERVFAIL`, so this is a request-level availability issue and panic/log amplification issue. If the `debug` plugin is enabled, the server disables the recovery path and this can crash the CoreDNS process.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/coredns/coredns"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "1.9.4"
}, {
"fixed" : "1.14.5"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 1.14.4"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/coredns/coredns/security/advisories/GHSA-74w3-63xv-x9mv"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-62994"
}, {
"type" : "WEB",
"url" : "https://github.com/coredns/coredns/pull/8207"
}, {
"type" : "WEB",
"url" : "https://github.com/coredns/coredns/commit/ab318db7b4a3a19273852ed627f54888198c8efb"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/coredns/coredns"
}, {
"type" : "WEB",
"url" : "https://github.com/coredns/coredns/releases/tag/v1.14.5"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-248", "CWE-755" ],
"severity" : "LOW",
"github_reviewed" : true,
"github_reviewed_at" : "2026-10-16T19:26:42Z",
"nvd_published_at" : "2026-07-16T20:16:47Z"
}
}