CVE-2026-62994: Github.com/coredns/coredns CoreDNS `k8s_external` Headless AXFR Can Vulnerability

LOW
Advisory Identifier: GHSA-74w3-63xv-x9mv
CVSS 3.7
CWE-755 CWE-248
Published
Advisory Summary

CVE-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

CVE Identifier CVE-2026-62994
GHSA Identifier GHSA-74w3-63xv-x9mv
Severity LOW
CVSS v3.1 Base Score 3.7
CWE Classification CWE-755: Improper Handling of Exceptional Conditions
Affected Product Github.com/coredns/coredns
Package Ecosystem Go
Published Date
Last Updated

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).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability Low (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.

github.com/coredns/coredns fixed: 1.14.5
Upgrade github.com/coredns/coredns to 1.14.5
Verify Direct and Transitive Dependencies

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

### Summary A 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. ### Details `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: - `plugin/transfer/transfer.go:129-131` ```go for records := range pchan { if x, ok := records[0].(*dns.SOA); ok && soa == nil { soa = x } ``` The 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: - `plugin/k8s_external/transfer.go:104-106` ```go recs, _ := e.srv(ctx, svcs, s) ch <- recs ``` `plugin/k8s_external/msg_to_dns.go` intentionally skips services whose port is `-1`: - `plugin/k8s_external/msg_to_dns.go:107-112` ```go for _, s := range services { // Don't add the entry if the port is -1 (invalid). The kubernetes plugin uses port -1 when a service/endpoint // does not have any declared ports. if s.Port == -1 { continue } ``` The Kubernetes EndpointSlice conversion creates that sentinel when an EndpointSlice has no ports: - `plugin/kubernetes/object/endpoint.go:62-66` ```go if len(ends.Ports) == 0 { // Add sentinel if there are no ports. e.Subsets[0].Ports = []EndpointPort{{Port: -1}} ``` This creates a built-in source-to-sink chain: 1. Kubernetes EndpointSlice has ready addresses and no ports. 2. CoreDNS `kubernetes` converts it to an endpoint with `Port: -1`. 3. `k8s_external` with `headless` includes that service in transfer data. 4. `k8s_external.Transfer` sends an empty SRV `[]dns.RR` batch. 5. `transfer.ServeDNS` indexes `records[0]` and panics. Suggested remediation: 1. In `plugin/transfer/transfer.go`, skip or reject empty `records` batches before indexing `records[0]`. 2. In `plugin/k8s_external/transfer.go`, only send `recs` when `len(recs) > 0` in the headless endpoint and port/protocol paths. 3. Add regression tests covering `Port: -1` headless services and empty transfer batches. Example consumer-side guard: ```go for records := range pchan { if len(records) == 0 { continue } if x, ok := records[0].(*dns.SOA); ok && soa == nil { soa = x } ... } ``` ### PoC Safe local unit-level reproduction was performed. It does not contact external infrastructure and does not require a live Kubernetes cluster. #### Reproducer A: the `transfer` sink panics on an empty batch A temporary package test was added under `plugin/transfer`: ```go package transfer import ( "context" "fmt" "testing" "github.com/coredns/coredns/plugin/pkg/dnstest" "github.com/coredns/coredns/plugin/test" "github.com/miekg/dns" ) type auditEmptyBatchTransferer struct{} func (auditEmptyBatchTransferer) Transfer(zone string, serial uint32) (<-chan []dns.RR, error) { if zone != "example.org." { return nil, ErrNotAuthoritative } ch := make(chan []dns.RR, 3) 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))} ch <- []dns.RR{} close(ch) return ch, nil } func TestAuditEmptyTransferBatchPanics(t *testing.T) { tr := &Transfer{ Transferers: []Transferer{auditEmptyBatchTransferer{}}, xfrs: []*xfr{{Zones: []string{"example.org."}, to: []string{"*"}}}, } req := new(dns.Msg) req.SetQuestion("example.org.", dns.TypeAXFR) rec := dnstest.NewRecorder(&test.ResponseWriter{TCP: true}) defer func() { if rec := recover(); rec == nil { t.Fatal("expected empty transfer batch to panic") } }() _, _ = tr.ServeDNS(context.Background(), rec, req) } ``` Verification command and result: ```text $ go test ./plugin/transfer -run TestAuditEmptyTransferBatchPanics -count=1 -v === RUN TestAuditEmptyTransferBatchPanics --- PASS: TestAuditEmptyTransferBatchPanics (0.00s) PASS ok github.com/coredns/coredns/plugin/transfer 0.005s ``` #### Reproducer B: the built-in `k8s_external` transferer emits the empty batch A temporary package test was added under `plugin/k8s_external`: ```go package external import ( "testing" "time" "github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/kubernetes" "github.com/coredns/coredns/request" "github.com/miekg/dns" ) func TestAuditHeadlessNoPortTransferEmitsEmptyBatch(t *testing.T) { e := New() e.Zones = []string{"example.com."} e.headless = true e.externalAddrFunc = func(request.Request, bool) []dns.RR { return nil } e.externalSerialFunc = func(string) uint32 { return 1 } e.externalServicesFunc = func(string, bool) ([]msg.Service, map[string][]msg.Service) { return nil, map[string][]msg.Service{ "/coredns/com/example/testns/headless/" + kubernetes.Endpoint: {{Host: "192.0.2.10", Port: -1, TTL: 5, Key: "/coredns/com/example/testns/headless/endpoint-a"}}, } } ch, err := e.Transfer("example.com.", 0) if err != nil { t.Fatalf("Transfer returned error: %v", err) } deadline := time.After(time.Second) for { select { case batch, ok := <-ch: if !ok { t.Fatal("transfer closed before emitting an empty batch") } if len(batch) == 0 { return } case <-deadline: t.Fatal("timed out waiting for an empty batch") } } } ``` Verification command and result: ```text $ go test ./plugin/k8s_external -run TestAuditHeadlessNoPortTransferEmitsEmptyBatch -count=1 -v === RUN TestAuditHeadlessNoPortTransferEmitsEmptyBatch --- PASS: TestAuditHeadlessNoPortTransferEmitsEmptyBatch (0.00s) PASS ok github.com/coredns/coredns/plugin/k8s_external 0.012s ``` Additional focused verification: ```text $ go test ./plugin/transfer ./plugin/k8s_external ./plugin/kubernetes -run 'TestAudit|^$' -count=1 -v ok github.com/coredns/coredns/plugin/transfer 0.006s ok github.com/coredns/coredns/plugin/k8s_external 0.013s ok github.com/coredns/coredns/plugin/kubernetes 0.116s ``` Affected package suites: ```text $ go test ./plugin/transfer ./plugin/k8s_external ./plugin/kubernetes -count=1 ok github.com/coredns/coredns/plugin/transfer 0.010s ok github.com/coredns/coredns/plugin/k8s_external 0.011s ok github.com/coredns/coredns/plugin/kubernetes 0.194s ``` ### Impact A network client allowed to request AXFR for the affected zone can trigger a panic in CoreDNS when all prerequisites are present. Required configuration/state: ```corefile example.com:53 { kubernetes cluster.local k8s_external example.com { headless } transfer example.com { to * } } ``` Additional prerequisite: - 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}`. In 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.

CVE-2026-62994 Affected Github.com/coredns/coredns Versions

1 component(s)
Fixed Versions:
1.14.5
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?
CVE-2026-62994 is a security vulnerability in Github.com/coredns/coredns involving CoreDNS `k8s_external` headless AXFR can emit an empty transfer batch that panics the `transfer` plugin under CWE-755: Improper Handling of Exceptional Conditions.
Which Github.com/coredns/coredns versions are affected?
Affected versions include releases prior to 1.14.5 across affected packages: github.com/coredns/coredns.
What version fixes CVE-2026-62994?
Upgrading to 1.14.5 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/coredns/coredns affected?
Yes, github.com/coredns/coredns is affected in releases prior to 1.14.5. Upgrade your dependency to 1.14.5 or later.
What is the CVSS score of CVE-2026-62994?
CVE-2026-62994 has a CVSS v3.1 base score of 3.7 (LOW) with vector string CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L.
What CWE is associated with CVE-2026-62994?
CVE-2026-62994 is classified under CWE-755: Improper Handling of Exceptional Conditions (The product does not handle or incorrectly handles an exceptional condition.).
How can I check Go dependencies for CVE-2026-62994?
Run the dependency tree command: 'inspect dependencies for github.com/coredns/coredns'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "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"
  }
}