Capsule: hostnameRegexHandler.OnUpdate validates stale (old) Tenant regex, allowing invalid AllowedHostnames regex to bypass webhook validation
Collected exploitation and prediction evidence
Risk source coverageCISA KEV is observed exploitation evidence. FIRST EPSS is a dated prediction of exploitation in the next 30 days. Missing scores are unknown. This collector requests predictions only for explicit CVE aliases in the public NuGet archive; Microsoft release views retain their existing prediction scope.
No collected risk observation for these identifiers. This does not establish absence of exploitation or a zero probability.
cve-program · CVE-2026-61795
PublishedCapsule is a multi-tenancy and policy-based framework for Kubernetes. From 0.13.0 until 0.13.7, hostnameRegexHandler.OnUpdate in internal/webhook/tenant/validation/hostname_regex.go reverses the new and old Tenant parameters and validates the previous AllowedHostnames.Regex instead of the submitted value. A cluster administrator can therefore store a malformed AllowedHostnames.Regex after the webhook accepts the update based on stale valid state. Subsequent Ingress creation or update reaches validate_hostnames.go, which evaluates the malformed pattern, ignores the regular-expression error, and treats every hostname as unmatched, blocking Ingress operations for the affected tenant until an administrator repairs the Tenant configuration. This issue is fixed in version 0.13.7.
- Source published
- 18 Sep 2026 16:31:42 UTC
- Source modified
- 21 Sep 2026 20:49:59 UTC
- Observed here
- 23 Oct 2026 15:48:27 UTC
- Source terms
- CVE Program Terms of Use
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: cve-program · CVE Program Terms of Use. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · 6.8 CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H
euvd · EUVD-2026-83166
PublishedCapsule is a multi-tenancy and policy-based framework for Kubernetes. From 0.13.0 until 0.13.7, hostnameRegexHandler.OnUpdate in internal/webhook/tenant/validation/hostname_regex.go reverses the new and old Tenant parameters and validates the previous AllowedHostnames.Regex instead of the submitted value. A cluster administrator can therefore store a malformed AllowedHostnames.Regex after the webhook accepts the update based on stale valid state. Subsequent Ingress creation or update reaches validate_hostnames.go, which evaluates the malformed pattern, ignores the regular-expression error, and treats every hostname as unmatched, blocking Ingress operations for the affected tenant until an administrator repairs the Tenant configuration. This issue is fixed in version 0.13.7.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Sep 18, 2026, 4:31:42 PM
dateUpdated: Sep 21, 2026, 8:49:59 PM
- Source published
- Sep 18, 2026, 4:31:42 PM (timezone not stated)
- Source modified
- Sep 21, 2026, 8:49:59 PM (timezone not stated)
- Observed here
- 11 Nov 2026 15:03:17 UTC
- Source terms
- ENISA EUVD · source terms and attribution apply
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: euvd · ENISA EUVD · source terms and attribution apply. Source text is retained; field grouping and change labels are provided by ChangeIntel.
EUVD CVSS 3.1 · 6.8 CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H
nvd · CVE-2026-61795
PublishedCapsule is a multi-tenancy and policy-based framework for Kubernetes. From 0.13.0 until 0.13.7, hostnameRegexHandler.OnUpdate in internal/webhook/tenant/validation/hostname_regex.go reverses the new and old Tenant parameters and validates the previous AllowedHostnames.Regex instead of the submitted value. A cluster administrator can therefore store a malformed AllowedHostnames.Regex after the webhook accepts the update based on stale valid state. Subsequent Ingress creation or update reaches validate_hostnames.go, which evaluates the malformed pattern, ignores the regular-expression error, and treats every hostname as unmatched, blocking Ingress operations for the affected tenant until an administrator repairs the Tenant configuration. This issue is fixed in version 0.13.7.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Deferred
Weakness classifications: CWE-697
Original publisher date fields
published: 2026-09-18T17:16:58.867
lastModified: 2026-09-24T21:25:27.050
- Source published
- 18 Sep 2026 17:16:58 UTC
- Source modified
- 24 Sep 2026 21:25:27 UTC
- Observed here
- 25 Oct 2026 14:19:28 UTC
- Source terms
- NIST NVD usage terms; underlying source attribution retained
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: nvd · NIST NVD usage terms; underlying source attribution retained. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS 3.1 · [email protected] · Secondary · 6.8 CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H
osv · GHSA-f94q-w3w8-cj67
Published### Summary A parameter order bug in `internal/webhook/tenant/validation/hostname_regex.go` causes the `hostnameRegexHandler.OnUpdate` webhook to validate the **old** Tenant object's `AllowedHostnames.Regex` instead of the **new** one being submitted. This allows an invalid (malformed) regex to bypass admission validation and be persisted to etcd, causing a Denial of Service for all Ingress operations within the affected tenant. ### Details The `TypedHandler[T]` interface defines `OnUpdate` as: ```go // handlers.go OnUpdate(c client.Client, reader client.Reader, obj T, old T, decoder admission.Decoder, recorder events.EventRecorder) Func // ^^^ NEW ^^^ OLD ``` The dispatcher in `handler.go:93` calls: ```go hndl.OnUpdate(c, reader, tnt, old, decoder, recorder) // ^^^ NEW ^^^ OLD ``` However, `hostnameRegexHandler.OnUpdate` in `hostname_regex.go` declares its parameters in **reversed order**: ```go // hostname_regex.go (BUGGY) func (h *hostnameRegexHandler) OnUpdate( _ client.Client, _ client.Reader, old *capsulev1beta2.Tenant, // ← receives NEW tenant (mislabeled as old) tnt *capsulev1beta2.Tenant, // ← receives OLD tenant (mislabeled as tnt) ... ) handlers.Func { return func(...) *admission.Response { if err := h.validate(tnt, req); err != nil { // ← validates OLD, not NEW return err } return nil } } ``` All 11 other handlers in the same package declare `(tnt, old)` correctly. `hostname_regex.go` is the only one with the swap. As a result, when a Cluster Admin updates `Tenant.Spec.IngressOptions.AllowedHostnames.Regex` to a malformed value, the webhook compiles the **previous valid regex** and returns `Allow`. The malformed regex is then written to etcd. Subsequently, every Ingress `CREATE` or `UPDATE` in that tenant triggers `validate_hostnames.go:160`: ```go matched, _ = regexp.MatchString(allowedRegex, currentHostname) ``` `regexp.MatchString` with an invalid pattern returns `(false, error)`. The error is silently ignored, `matched` is `false`, and **every hostname is rejected** — blocking all Ingress operations in the tenant until the Tenant object is manually corrected by an admin. ### PoC ``` //go:build ignore // Standalone reproducer for hostname_regex.go argument swap bug in Capsule // No external deps - shows the bug logic using only stdlib package main import ( "fmt" "regexp" ) // Simulating the Tenant spec structure type AllowedHostnames struct { Regex string } type IngressOptions struct { AllowedHostnames *AllowedHostnames } type TenantSpec struct { IngressOptions IngressOptions } type Tenant struct { Name string Spec TenantSpec } // ========================================================= // BUGGY implementation (hostname_regex.go as-is) // OnUpdate(_, _, old *Tenant, tnt *Tenant) → validates OLD // ========================================================= func hostnameValidate(tnt *Tenant) error { if tnt.Spec.IngressOptions.AllowedHostnames == nil { return nil } if len(tnt.Spec.IngressOptions.AllowedHostnames.Regex) == 0 { return nil } _, err := regexp.Compile(tnt.Spec.IngressOptions.AllowedHostnames.Regex) if err != nil { return fmt.Errorf("Deny: unable to compile allowedHostnames allowedRegex") } return nil } // Dispatcher calls: OnUpdate(c, reader, newTenant, oldTenant, ...) // Interface says: OnUpdate(c, reader, obj[NEW], old[OLD], ...) // // BUGGY handler receives: (old, tnt) meaning: // 3rd param (labeled "old") = actually NEW // 4th param (labeled "tnt") = actually OLD // Then calls h.validate(tnt) = validates the OLD tenant func buggyOnUpdate(newTenant, oldTenant *Tenant) error { // BUG: parameters are SWAPPED vs the interface contract old := newTenant // dispatcher's "new" arrives as "old" in this function tnt := oldTenant // dispatcher's "old" arrives as "tnt" in this function _ = old // unused in the real code too return hostnameValidate(tnt) // validates OLD, not NEW } // CORRECT implementation (what it should be) func correctOnUpdate(newTenant, oldTenant *Tenant) error { _ = oldTenant return hostnameValidate(newTenant) // validates NEW } // Simulate ingress hostname validation AFTER bad regex is stored func validateIngressHostname(tenant *Tenant, hostname string) bool { if tenant.Spec.IngressOptions.AllowedHostnames == nil { return true } allowedRegex := tenant.Spec.IngressOptions.AllowedHostnames.Regex if len(allowedRegex) == 0 { return true } // This is validate_hostnames.go:160 - error is IGNORED matched, _ := regexp.MatchString(allowedRegex, hostname) return matched } func main() { fmt.Println("=== Capsule Bug Reproducer: hostname_regex.go argument swap ===") fmt.Println() oldTenant := &Tenant{ Name: "demo-tenant", Spec: TenantSpec{ IngressOptions: IngressOptions{ AllowedHostnames: &AllowedHostnames{ Regex: `^[\w.-]+\.example\.com$`, // valid regex }, }, }, } // Attacker (cluster admin) sets an INVALID regex in the new spec newTenant := &Tenant{ Name: "demo-tenant", Spec: TenantSpec{ IngressOptions: IngressOptions{ AllowedHostnames: &AllowedHostnames{ Regex: `[invalid-regex(`, // INVALID regex }, }, }, } fmt.Printf("Old tenant regex: %q (valid)\n", oldTenant.Spec.IngressOptions.AllowedHostnames.Regex) fmt.Printf("New tenant regex: %q (INVALID)\n", newTenant.Spec.IngressOptions.AllowedHostnames.Regex) fmt.Println() // Step 1: Webhook runs OnUpdate fmt.Println("--- Step 1: Webhook OnUpdate ---") err := buggyOnUpdate(newTenant, oldTenant) if err != nil { fmt.Printf("[BUGGY] Webhook DENIES update: %v\n", err) } else { fmt.Println("[BUGGY] Webhook ALLOWS update (validates OLD regex) ← WRONG") } err = correctOnUpdate(newTenant, oldTenant) if err != nil { fmt.Printf("[CORRECT] Webhook DENIES update: %v ← EXPECTED\n", err) } else { fmt.Println("[CORRECT] Webhook ALLOWS update") } // Step 2: Invalid regex now stored in etcd - simulate ingress validation fmt.Println() fmt.Println("--- Step 2: Ingress creation after bad regex stored ---") storedTenant := newTenant // bad regex is now in etcd hostnames := []string{ "app.example.com", "api.example.com", "evil.attacker.com", } for _, h := range hostnames { allowed := validateIngressHostname(storedTenant, h) fmt.Printf(" Ingress hostname %q → allowed=%v", h, allowed) if !allowed { fmt.Print(" ← BLOCKED (DoS: invalid regex causes all hostnames to fail)") } fmt.Println() } fmt.Println() fmt.Println("=== Result ===") fmt.Println("Invalid regex bypasses webhook validation and gets stored.") fmt.Println("All subsequent Ingress create/update in this tenant are BLOCKED.") fmt.Println("CWE-697: Incorrect Comparison — wrong Tenant object is validated.") } ``` ```go // Simulates the buggy webhook behaviour oldTenant := &Tenant{AllowedRegex: `^[\w-]+\.example\.com$`} // valid newTenant := &Tenant{AllowedRegex: `[invalid-regex(`} // malformed // Buggy OnUpdate: validates oldTenant (valid) → ALLOW // Correct OnUpdate: validates newTenant (invalid) → DENY // After malformed regex is stored, all ingress hostnames are rejected: matched, _ := regexp.MatchString(`[invalid-regex(`, "app.example.com") // matched = false, error ignored → Ingress blocked ``` ### Fix Swap the parameter names in `hostname_regex.go` to match the interface contract: ```go // BEFORE (buggy) func (h *hostnameRegexHandler) OnUpdate( _ client.Client, _ client.Reader, old *capsulev1beta2.Tenant, tnt *capsulev1beta2.Tenant, ... // AFTER (fixed) func (h *hostnameRegexHandler) OnUpdate( _ client.Client, _ client.Reader, tnt *capsulev1beta2.Tenant, old *capsulev1beta2.Tenant, ... ``` ### Impact A Cluster Admin (or a compromised admin account) can — intentionally or via a typo — set a malformed `AllowedHostnames.Regex` on any Tenant. The webhook silently accepts the update. All users in the affected tenant are subsequently unable to create or update any Ingress resource until an admin manually corrects the Tenant spec. This constitutes a targeted Denial of Service against the tenant's ingress layer.
- Source published
- 18 Sep 2026 17:14:43 UTC
- Source modified
- 28 Sep 2026 17:11:18 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- GitHub Advisory Database · CC-BY-4.0
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · GitHub Advisory Database · CC-BY-4.0. Source text is retained; field grouping and change labels are provided by ChangeIntel.
CVSS_V3 · CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:N/I:N/A:H
Publisher rating · MODERATE
osv · GO-2026-6519
PublishedCapsule: hostnameRegexHandler.OnUpdate validates stale (old) Tenant regex, allowing invalid AllowedHostnames regex to bypass webhook validation in github.com/projectcapsule/capsule
- Source published
- 28 Sep 2026 16:43:40 UTC
- Source modified
- 28 Sep 2026 17:00:17 UTC
- Observed here
- 24 Oct 2026 13:19:41 UTC
- Source terms
- OSV · upstream source license applies
Original source record · Cite this exact observation · Complete original publisher JSON
This view projects the publisher's source fields for reading; the original JSON retains additional fields and CVE enrichment containers. Attribution: osv · OSV · upstream source license applies. Source text is retained; field grouping and change labels are provided by ChangeIntel.