Skip to main content

CVE-2026-25518: The cert-manager Bug That Lets a Poisoned DNS Reply Crash Your Whole TLS Pipeline

9 min readDora NodaDora Noda
Share
On this page

CVE-2026-25518 is short on drama and long on consequence: a crafted DNS response, cached by cert-manager during a routine ACME DNS-01 self-check, triggers a panic and kills the controller process. No credential theft, no certificate forgery — just a denial of service against the one component that issues and renews TLS for every tenant's custom domain on a self-hosted PaaS. The bug shipped in cert-manager v1.18.0, was fixed in v1.19.3 and v1.18.5 on February 2, 2026, and the fix alone doesn't close the door — DNS-over-HTTPS has to be turned on separately, and even then one attack path stays open.

If you run cert-manager on a Cluster API fleet, Kubernetes-native TLS automation, or anything issuing certificates via DNS-01, here's exactly what broke, why a controller crash matters more than it sounds, and the two config changes to verify today.


What the vulnerability actually does

DNS-01 validation doesn't just ask an ACME server to check a TXT record and trust it blindly — cert-manager runs its own pre-flight self-check first, querying DNS directly to confirm the challenge record has propagated before it tells Let's Encrypt (or any ACME CA) to validate. That self-check means the cert-manager controller itself performs live DNS lookups: first querying NS records to find the domain's authoritative nameservers, then querying those nameservers directly for the TXT record, caching results along the way. According to the GitHub security advisory, by default all of that happens over plain, unencrypted DNS.

That's the opening. An attacker positioned to intercept and modify the controller's outbound DNS traffic — an on-path attacker on the pod's network egress, or, more insidiously, whoever controls the authoritative nameserver for the domain being validated — can insert a specially crafted DNS response. cert-manager caches it. The moment that cached entry is read back out, in an order the code didn't expect, the process panics. Kubernetes restarts the container, and if the attacker can repeat the trick, they can hold the controller in a crash loop indefinitely.

There's no data exfiltration and no forged certificate here — this is a pure availability bug against your issuance and renewal pipeline. The release notes describe the underlying defect plainly: a DNS response cached in an order the controller's code didn't anticipate causes a panic the next time that cache entry is read — the kind of index-out-of-bounds or nil-dereference bug that a fuzzer would eventually have found, except here an attacker gets to choose the ordering on purpose. Affected versions run from v1.18.0 up through the versions before the fix; anything older than v1.18.0 never shipped the vulnerable code path. The patches landed in v1.19.3 and v1.18.5, both released February 2, 2026.

Two things make this worth a same-day fix rather than a "patch in the next maintenance window" shrug. It's remotely triggerable by anyone who can get on-path for the controller's DNS egress, which on a lot of cluster network topologies is a wider set of positions than teams assume — a compromised node, a misconfigured CNI policy, or a shared VPC segment can all put an attacker close enough. And it's triggerable without any credentials, API access, or prior foothold in the cluster at all if the attacker instead controls the authoritative nameserver for a domain your controller is validating — which, on a platform that lets tenants point arbitrary custom domains at their apps, is a scenario the platform doesn't get to assume away.

Why a controller crash is a bigger deal than a crash

A single cert-manager pod restarting sounds like a Tuesday. It isn't, for two reasons specific to how the controller queues work and to where the industry's certificate lifetimes are headed.

First, the queue doesn't fail gracefully — it fails slowly. cert-manager retries ephemeral failures on a short delay (up to five minutes), but once an issuance attempt is judged to have failed terminally, it backs off exponentially — from one hour up to 32 hours by default — before trying again. A crash mid-validation doesn't just cost you the seconds the pod takes to restart; if the crash lands your in-flight CertificateRequest in the wrong bucket, a legitimate renewal can end up parked for hours while the backoff timer runs out. On a multi-tenant platform, one controller crash-loop doesn't touch one certificate — it stalls the renewal queue for every tenant's custom domain waiting behind it, because there's one controller reconciling all of them.

Second, the fuse got shorter industry-wide at almost the same time this bug was found. Let's Encrypt's default certificate lifetime is dropping from 90 days toward 45 by 2029, and its opt-in shortlived profile — 160 hours, about six days — went GA in January 2026, a few weeks before this CVE's fix shipped. A platform running 90-day certificates can absorb a multi-hour renewal stall without anyone noticing; a platform that's opted a tenant domain into six-day certificates has a renewal cadence measured in single-digit days, and a DoS that parks renewals in an exponential backoff queue eats a meaningfully larger fraction of the certificate's total remaining life. The math that made short-lived certs attractive — smaller compromise windows — is the same math that makes an ACME-automation DoS more dangerous the more aggressively you've adopted them.

The concrete fix: patch, then turn on DoH

Two separate actions, not one. Patching alone removes the specific panic; it does not encrypt the DNS traffic the panic exploited.

1. Upgrade. Move to cert-manager v1.19.3 or later on the 1.19 line, or v1.18.5 or later if you're pinned to 1.18. Anything on 1.17 or earlier was never affected and doesn't need a security-driven upgrade for this CVE specifically, but you're missing two minor versions of everything else by staying there.

2. Turn on DNS-over-HTTPS for the self-check resolver. This is the step teams skip because the advisory frames it as a mitigation rather than the fix, and because it means editing controller flags most fleets never touch after initial setup. cert-manager's --dns01-recursive-nameservers flag accepts DoH endpoints directly:

text
--dns01-recursive-nameservers=https://1.1.1.1/dns-query,https://8.8.8.8/dns-query
--dns01-recursive-nameservers-only=true

The first flag replaces the default /etc/resolv.conf-sourced resolvers with an explicit, encrypted pair. The second forces cert-manager to use only those recursive resolvers instead of also querying authoritative nameservers directly for the self-check — which matters, because that direct-to-authoritative query is the second attack path DoH doesn't close.

What DoH doesn't fix: if the domain under validation has its authoritative nameserver controlled by a malicious actor — a plausible scenario if you're issuing certificates for tenant-supplied custom domains and a tenant (or an attacker who's hijacked a tenant's DNS) points their zone at hostile infrastructure — that nameserver can still hand back a crafted response over an otherwise-encrypted channel, because the channel isn't the trust boundary in that case, the server is. DoH protects the path between your controller and the recursive resolver; it does not vet who's authoritative for a domain someone else asked you to validate. On a self-hosted PaaS accepting tenant-supplied custom domains as a matter of course, that residual risk is exactly the shape of the platform's threat model, not an edge case.

An audit checklist for a Cluster API fleet

If you're running cert-manager as the TLS automation layer behind a self-hosted, git-push PaaS on Cluster API-managed nodes, four checks cover this CVE end to end:

  • Version. kubectl get deployment cert-manager -n cert-manager -o jsonpath='{.spec.template.spec.containers[0].image}' — confirm it resolves to v1.19.3+ or v1.18.5+.
  • Resolver config. Check the controller's launch flags (kubectl get deployment cert-manager -n cert-manager -o yaml | grep dns01-recursive) for whether --dns01-recursive-nameservers is set at all. Unset means default /etc/resolv.conf resolvers over plain DNS — exactly the exposed configuration.
  • -only flag. Setting DoH endpoints without also setting --dns01-recursive-nameservers-only=true leaves the direct-to-authoritative-nameserver query path active over plain DNS, which is the path DoH can't protect anyway — but it's worth confirming you're not silently mixing encrypted and unencrypted lookups for no reason.
  • Alerting on crash-loops. A platform issuing certificates for tenant custom domains should already alert on cert-manager pod restarts; this CVE is the concrete reason why a restart count that used to be background noise deserves a page. If your monitoring doesn't distinguish "cert-manager restarted once during a routine deploy" from "cert-manager is crash-looping every 90 seconds," add that distinction now — it's the observable signature of exactly this exploit in progress.

None of these four checks require downtime or a maintenance window. They're a kubectl get, a flag edit, and a Deployment rollout — the kind of change that should ship the same day you read the advisory, not queued behind a quarterly cluster-maintenance cycle.

Why this matters more once an agent is holding the pen

The same audit checklist reads differently the moment a fleet's day-to-day operations shift from a human running kubectl to an AI agent deploying apps and provisioning custom domains through an MCP tool call. A human operator who notices cert-manager crash-looping investigates; an agent wiring up a new tenant's custom domain via an API call has no reason to notice a stalled renewal queue unless the platform surfaces it as a first-class signal, because "the certificate will renew eventually" is exactly the kind of eventually-consistent assumption that doesn't hold up against a queue stuck behind a 32-hour backoff timer. A platform whose control plane is meant to be agent-operable has to treat ACME automation health as an observable, queryable piece of state — not a Kubernetes implementation detail three layers below what the agent's tool schema exposes — precisely because the failure mode this CVE demonstrates is silent from the outside until a tenant's certificate actually expires.

That's also the argument for owning the whole TLS path rather than delegating it to a managed platform's opaque renewal pipeline: when the fix requires a specific flag change on a specific controller Deployment, a team that operates its own cert-manager instance can verify and ship that change in an afternoon. A team on a platform where ACME automation is someone else's implementation detail can only wait for a changelog entry and hope the vendor's default resolver configuration was already correct.


Bex.co runs its own ACME automation on Cluster API-managed Hetzner infrastructure as part of owning the full TLS path for every tenant's custom domain — patched controller versions and DoH-only DNS-01 resolution aren't optional configuration, they're part of the default fleet baseline. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex