Here's an uncomfortable fact about the Kubernetes RBAC you probably already handed out: if you ever bound a tenant's namespace to Kubernetes' built-in edit or admin ClusterRole — one of the most common ways to give a tenant self-service access to their own namespace — you may have also handed them, without ever intending to, the ability to steal another tenant's DNS provider credentials. Not through a misconfiguration. Through a label.
That's the shape of CVE-2026-62290 (GHSA-8rvj-mm4h-c258), a high-severity (CVSS 7.3, CWE-863 — incorrect authorization) cert-manager bug reported by researchers kamil-sawicki, robertprast, and EQSTLab, and patched simultaneously across two release branches — v1.19.6 and v1.20.3 — on June 25, 2026. If you run cert-manager for multi-tenant TLS — which is exactly what a self-hosted PaaS issuing automated certificates for every tenant's custom domain does — the fix alone doesn't close the gap. The RBAC pattern that let this bug reach further than anyone granted is still sitting in most clusters. Below is the mechanism, then a concrete checklist to audit your own install for it.
The Bug: A Challenge Can Carry Its Own Solver Config
Normally, issuing a certificate walks a fixed pipeline: Certificate → CertificateRequest → Order → Challenge. cert-manager picks the DNS-01 or HTTP-01 solver by matching the request against the rules on the referenced Issuer or ClusterIssuer — dnsZones, dnsNames, matchLabels — so a tenant issuing foo.tenant-a.example.com can only ever trigger the solver scoped to tenant-a's zone.
The bug is that cert-manager also lets a Challenge object be created directly, skipping that selection step entirely. A user who can create a Challenge (acme.cert-manager.io/v1) supplies spec.solver themselves — arbitrary DNS-01 or HTTP-01 configuration of their own choosing. When that Challenge references a shared ClusterIssuer, cert-manager still derives the credential namespace and ambient-credential policy from the ClusterIssuer, but the solver configuration itself is whatever the attacker wrote — so none of the Issuer's dnsZones/dnsNames/matchLabels constraints apply. The advisory calls out the acme-dns provider specifically: a crafted Challenge can point the DNS-01 solver's account-secret lookup at an attacker-controlled acme-dns host, which then receives the X-Api-User and X-Api-Key headers straight from the ClusterIssuer's own credential secret.
There's a second variant: a user who can update an Order (pre-patch, that verb was also granted) can rewrite spec.issuerRef to point at a different ClusterIssuer than the one that created it. The next time cert-manager reconciles and recreates the Challenge, it repeats the same credential-loading step against the newly-referenced issuer — same exfiltration path, different entry point.
Nothing about this requires cluster-admin access, a compromised pod, or a misread YAML file. It requires exactly what a self-hosted PaaS already grants tenants every day: the ability to create objects in their own namespace.
Why It Reached Further Than Anyone Meant to Grant
The part worth sitting with is how namespace users got create on challenges.acme.cert-manager.io and create/patch/update on orders.acme.cert-manager.io in the first place. Nobody wrote a RoleBinding naming cert-manager-edit. The cert-manager-edit ClusterRole ships with two labels:
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-admin: "true"Kubernetes' own ClusterRoleAggregationRule mechanism means any ClusterRole carrying those labels gets its rules folded into the built-in edit and admin ClusterRoles automatically, cluster-wide, the moment cert-manager is installed. So the actual grant chain looks like this: a platform operator runs kubectl create rolebinding tenant-a-access --clusterrole=edit --serviceaccount=tenant-a:default -n tenant-a — an entirely ordinary way to give a tenant self-service control of their own namespace, with zero mention of cert-manager anywhere in that command — and the tenant inherits create on ACME Challenges and Orders as a side effect of a label they never saw. This is precisely the aggregation behavior that makes Kubernetes ClusterRole composition powerful for legitimate extension, and precisely what makes it dangerous when a shared, cluster-scoped credential source (a ClusterIssuer) sits downstream of an aggregated permission nobody audited.
This isn't a contrived setup. It's close to the default shape of tenant onboarding on any self-hosted PaaS that gives each customer their own namespace: provision the namespace, drop in a ServiceAccount, bind it to edit (or admin, if the tenant also needs to manage RBAC inside their own namespace) so the tenant's dashboard or CLI can create Deployments, Services, and Secrets without the platform hand-rolling a bespoke Role for every resource type a tenant might touch. That convenience is exactly why edit/admin are the default answer for tenant self-service in the first place — and exactly why an aggregation label on a completely unrelated add-on's ClusterRole silently rides along with it.
The June 25, 2026 patch fixes this narrowly: cert-manager-edit no longer grants create on challenges.acme.cert-manager.io, and no longer grants create, patch, or update on orders.acme.cert-manager.io (the patch/update verbs stay on Challenges only so users can clear stuck finalizers — the spec itself is immutable post-creation). Upgrading closes the hole for cert-manager's own shipped RBAC. It does nothing to tell you whether your platform re-granted the same permissions somewhere else, which the release notes flag as a real possibility: "if you have tooling or workflows that create Challenge or Order resources directly... you will need to grant those permissions explicitly" post-upgrade — the exact re-opening a platform team could do by accident while restoring a workflow that broke.
The Audit Checklist: Six Checks for Any Multi-Tenant cert-manager Install
Patching is step zero. The rest is verifying the blast radius this CVE-class bug exploited isn't still reachable through a path cert-manager's own fix can't see.
1. Confirm the installed version is actually patched.
kubectl get pods -n cert-manager -l app=cert-manager \
-o jsonpath='{.items[0].spec.containers[0].image}'Confirm the tag is v1.19.6/v1.19.x (≥.6), v1.20.3/v1.20.x (≥.3), or any v1.21.0+ release — all of these ship the fix. Anything in v1.18.0–v1.20.2 is vulnerable.
2. Find every subject bound to edit, admin, or cert-manager-edit — the actual inheritance path.
kubectl get clusterrolebindings,rolebindings -A -o json \
| jq -r '.items[]
| select(.roleRef.name == "edit" or .roleRef.name == "admin" or .roleRef.name == "cert-manager-edit")
| "\(.metadata.namespace // "cluster-scoped")/\(.metadata.name) -> \(.roleRef.name)"'Every line this prints is a subject who inherited the pre-patch create grant before you upgraded, and who inherits whatever cert-manager-edit grants going forward. If this list includes tenant service accounts — it will, on any platform using edit/admin for tenant self-service — that's the population who could have exploited this bug pre-patch, and needs the rest of this checklist run against them.
3. Verify the currently-installed cert-manager-edit no longer grants the removed verbs.
kubectl get clusterrole cert-manager-edit -o json \
| jq '.rules[] | select(.apiGroups[]? == "acme.cert-manager.io")'Confirm challenges has no create verb, and orders has no create, patch, or update verbs. If any of those are present, either the Helm chart hasn't actually been upgraded (version string in check 1 can lag a helm upgrade that didn't apply CRD/RBAC templates), or something re-added them.
4. Search for a custom ClusterRole or Role that quietly re-grants the same verbs.
kubectl get clusterroles,roles -A -o json \
| jq -r '.items[]
| select(.rules[]? | (.apiGroups[]? == "acme.cert-manager.io") and (.verbs[]? == "create"))
| .metadata.name'This is the check that catches the release-notes warning above: a platform team restoring a broken CI workflow by writing its own challenges-writer ClusterRole and binding it broadly — reopening the exact hole the patch just closed, one layer removed from cert-manager's own shipped RBAC.
5. Confirm no tenant namespace has direct read/list access to another tenant's DNS-solver secret.
kubectl get rolebindings,clusterrolebindings -A -o json \
| jq -r '.items[] | select(.roleRef.name | test("secret"; "i")) | .metadata.name'Cross-reference results against where each ClusterIssuer's dns01.<provider>.secretName actually lives. This is the same credential the Challenge bug was exfiltrating indirectly — worth confirming it isn't also reachable directly.
6. Decide whether a shared ClusterIssuer should still be shared.
If every tenant's custom-domain certs route through one ClusterIssuer, every tenant's Challenge/Order objects share that same credential-loading step — meaning any future bug in this same class has the same fleet-wide blast radius this one did. Per-namespace Issuer resources (cert-manager's namespace-scoped alternative) confine the credential a compromised or overly-permissioned tenant can reach to their own zone, at the cost of provisioning one Issuer per tenant instead of one ClusterIssuer for the fleet.
Defense in Depth Beyond the Patch
The patch and the six checks above close the specific path this CVE exploited. Two structural changes reduce exposure to the next one in the same class:
- Admission policy as a backstop, not just RBAC. An OPA Gatekeeper or Kyverno policy that blocks direct
Challenge/Ordercreation outside the controller's own service account enforces the "these are internal, not user-facing" intent structurally — so a future RBAC regression (an aggregation label nobody re-audited, a ClusterRole nobody meant to reopen) doesn't automatically become an exploitable path the way this one did. - Namespaced
Issuerover sharedClusterIssuerwherever the operational overhead is acceptable, precisely because it turns "one bug, every tenant's credentials" into "one bug, one tenant's credentials."
For a self-hosted PaaS whose entire pitch is automated TLS for every tenant's custom domain on a shared control plane, this is exactly the kind of shared-infrastructure blast radius worth auditing before it's someone else's incident report. Bex.co runs its own tenant TLS through this same cert-manager/ACME stack on Cluster-API-managed Hetzner fleets — being open source means that RBAC surface is inspectable rather than a vendor's word for it, but it doesn't audit itself. Run the six checks above against your own install regardless of who wrote the control plane underneath it.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



