Capsule tenant-resource escalation and Symfony sanitizer URL-attribute batch (GHSA, 2026-05-28)¶
Signal: GitHub Advisory Database published a May 28 batch with reusable offensive-operator value for Kubernetes multi-tenant control planes and rich-text sanitizers. The durable lesson is boundary mismatch: namespacing a cluster-scoped object does not constrain it, namespace subresources can bypass namespace webhooks, and sanitizer URL checks must cover every URL-valued attribute the allowlist admits.
Promoted items:
GHSA-qjjm-7j9w-pw72/CVE-2026-22872: Capsule< 0.13.0TenantResource.spec.rawItemscan ask the cluster-admin controller to create cluster-scoped resources such asClusterRoleorValidatingWebhookConfiguration;obj.SetNamespace()is ignored by Kubernetes for cluster-scoped kinds.GHSA-2ww6-hf35-mfjm/CVE-2026-30963: Capsule< 0.13.0namespace ownership validation missesnamespaces/statusandnamespaces/finalize, allowing namespace hijack when a tenant admin has patch/update rights on those subresources.GHSA-hhg7-c65m-h7ff/CVE-2026-45753: Symfonyhtml-sanitizer/symfony/symfonyURL scheme validation omittedaction,formaction,poster, andcite; permissive configs can preservejavascript:URIs in those attributes.
Use this only in authorized tests. Keep proofs minimal: create harmless marker objects in lab or scoped test tenants, use tester-owned callback URLs, and stop before cross-tenant data access, webhook disruption, credential access, or production cluster changes.
Capsule tenant admission-bypass trio (Sept 18 follow-up)¶
A September 18 Capsule wave (v0.13.5) adds three reusable admission-webhook bypass axes for multi-tenant Kubernetes recon from a tenant-owner position:
- Forbidden-metadata denylist miss via sort/search mismatch — GHSA-gjw4-3v3v-rqxg / CVE-2026-61672 (CVSS 7.1):
ForbiddenListSpec.ExactMatchsorts the denied-key list case-insensitively (strings.ToLowercomparator) then binary-searches withsort.SearchStrings, which assumes byte order. Any denylist mixing capitalised and lowercase keys (ASCII uppercase 0x41–0x5A sorts before lowercase 0x61–0x7A by byte, interleaved byToLower) makes the search land on the wrong index and return false for a key literally present in the list. A tenant owner can then set forbidden labels/annotations (PSA labels,kubernetes.io/metadata.name, LB/scheduler/vendor annotations) on their own namespaces/Services. Operator rule: when probing any exact-match denylist (labels, annotations, WAF headers, filename blocks), vary key case — a denylist containing bothFooandbaris the classic trigger; test the mixed-case entry, not just the lowercase one. - OnUpdate handler validating the OLD object — GHSA-f94q-w3w8-cj67 / CVE-2026-61795: a swapped parameter declaration makes
hostnameRegexHandler.OnUpdatecompile the tenant's previousAllowedHostnames.Regexand allow the malformed new one into etcd (then every Ingress in the tenant is rejected becauseregexp.MatchStringerrors are silently swallowed). Validation heuristic for any update path on any platform: pair a valid old value with an invalid new value and diff whether the rejection follows the new bytes; also try the reverse pair to prove which object each branch reads. - Wrong-field compile in a validation loop — GHSA-gxjc-74v5-3vx3 / CVE-2026-61794: the annotations-regex webhook loop compiles the labels regex for both iterations, so a malformed
ForbiddenAnnotations.Regexpersists whenever labels regex is valid. Same sweep rule as the siblingdynamicDataFieldlesson: when a fix/validation covers sibling fields A and B, always test A-valid + B-invalid, never assume the loop body reads the loop variable.
Keep proofs tenant-scoped in a lab cluster: submit mixed-case forbidden-label canaries and malformed-regex Tenant updates against your own test tenants, capture the admission allow/deny decision table, and restore Tenant objects immediately (a persisted malformed regex bricks Ingress admission for that tenant — recoverable, but do it only where authorized).
Operator checklist¶
1. Capsule TenantResource.rawItems cluster-scope privilege boundary¶
Where to look:
- Kubernetes clusters using Capsule / Project Capsule versions before
0.13.0. - Multi-tenant clusters where tenant owners can create or modify
TenantResourceobjects. - Capsule deployments whose controller service account is bound to broad or
cluster-adminprivileges.
Safe validation path:
- Confirm Capsule version and controller RBAC with read-only evidence where possible: Helm values, manifests, SBOMs,
kubectl get deploy -n capsule-system, andkubectl get clusterrolebinding capsule-manager-rolebinding -o yaml. - Confirm the test tenant identity can create
tenantresourcesbut cannot directly create a chosen cluster-scoped marker kind, for examplekubectl auth can-i create tenantresources --as <tenant-user> -n <tenant-ns>andkubectl auth can-i create clusterroles --as <tenant-user>. - In a lab or explicitly scoped test cluster, submit a
TenantResourcewith a harmless cluster-scoped marker such as aClusterRolenamed with a tester prefix and no dangerous verbs. - Verify whether the Capsule controller creates the cluster-scoped object despite the tenant namespace being set in the raw item processing path.
- Delete the marker object immediately after capture. Do not create admission webhooks, wildcard RBAC, secret-reading roles, or objects that affect unrelated tenants unless the program explicitly provides a safe sandbox for that proof.
Evidence to capture:
- Capsule version and controller RBAC binding.
- Tenant user permissions showing the direct action is denied.
- The minimal
TenantResourcemanifest and the resulting cluster-scoped marker object. - Controller logs or events linking the object creation to the Capsule controller.
Reporting heuristic: strong reports prove tenant-scoped input plus controller cluster-admin authority plus cluster-scoped output that the tenant could not create directly. Frame impact as cross-tenant privilege escalation / cluster control-plane object creation, not as a generic Kubernetes misconfiguration.
2. Capsule namespace subresource hijack boundary¶
Where to look:
- Capsule versions before
0.13.0that rely on validating webhooks to prevent namespace ownership changes. - Tenants or delegated roles with patch/update access to
namespaces/statusornamespaces/finalize. - Clusters exposing self-service namespace or tenant lifecycle automation where subresource rights may have been granted indirectly.
Safe validation path:
- Confirm the webhook rules include
namespacesbut not the relevant namespace subresources. - Confirm the attacker-like tenant identity has patch rights on
namespaces/statusornamespaces/finalize; without those rights, document it as not exploitable in that environment. - In a lab or program-provided sandbox, create two test tenants and a disposable namespace owned by the first tenant.
- Patch only the disposable namespace's metadata owner reference through the status or finalize subresource to point to the second tenant.
- Verify Capsule accepts the ownership change while the normal namespace update path would have been rejected.
Evidence to capture:
- Webhook rule set and missing subresource coverage.
kubectl auth can-i patch namespaces/statusor equivalent permission proof.- Before/after owner references for a disposable namespace.
- Capsule tenant visibility or access change after the patch.
Reporting heuristic: this is high-signal only when the target grants namespace subresource mutation to a tenant-like principal. Without that permission, keep it as a configuration exposure note rather than a confirmed exploit path.
3. Symfony html-sanitizer missing URL-attribute scheme checks¶
Where to look:
- Composer projects using
symfony/html-sanitizerorsymfony/symfonyin>= 6.1.0, < 6.4.40,>= 7.0.0, < 7.4.12, or>= 8.0.0, < 8.0.12. - Rich-text fields, CMS blocks, ticket/comment systems, profile bios, documentation portals, email templates, and WYSIWYG preview surfaces that allow custom HTML.
- Configurations using
allowStaticElements(), wildcard attributes, or explicitallowAttribute()/allowElement()calls forform[action],button[formaction],input[formaction],video[poster], or quote/change tags withcite.
Safe validation path:
- Confirm the sanitizer version and that the dangerous attribute is intentionally admitted by configuration.
allowSafeElements()alone is not enough for theform/formactionpath. - Submit inert HTML in a tester-controlled field, using visible text and a non-exfiltrating marker. Examples: a form with
action="javascript:console.log('skillz-marker')", a button withformaction, or a quote element withcite="javascript:...". - Inspect the sanitized output before clicking anything. The core proof is that a
javascript:or otherwise off-policy URL survives in an admitted URL attribute. - For
action/formaction, if interaction proof is needed, use a disposable test account and a harmless marker only; do not phish or trigger actions from real users. - Check propagation into stored pages, previews, exports, notifications, and webviews, because sanitizer results often travel beyond the original input surface.
Evidence to capture:
- Package version, sanitizer configuration, and input location.
- Raw HTML and sanitized/rendered HTML showing the preserved URL-valued attribute.
- Whether exploitation requires user interaction, same-origin script execution, or only visual/navigation deception.
- Output context: stored, reflected, preview-only, email, export, or webview.
Reporting heuristic: strong reports show untrusted HTML plus a permissive sanitizer allowlist plus an unvalidated URL-valued attribute that reaches a user-viewed context. Separate script execution from click-required form/navigation impact.
Non-signal this hour¶
Reviewed but not promoted as standalone Skillz guidance:
GHSA-5wrp-cwcj-q835/CVE-2026-41178OpenTelemetry Go baggage header parsing CPU/log amplification. It is remotely reachable in instrumented services but availability-only, bounded by upstream header limits, and not a durable offensive workflow for this wiki.- CISA KEV advanced to catalog
2026.05.28, but the top entries remained the May 27 Nx Console, TanStack, and Daemon Tools Lite items plus previously reviewed LiteSpeed / Drupal / Langflow entries; no new Skillz operator workflow was added. - PortSwigger Research stayed on Top 10 web hacking techniques of 2025.
- Trail of Bits stayed on the already-covered zizmor GitHub Actions static-analysis article.
- ProjectDiscovery
/blog/rssstayed on already-covered Neo / Nuclei / DAST material;/blog/rss.xmlstill returned 404. - GitHub Security Blog stayed GHES signing-key rotation / incident-response oriented.
- Disclosed sitemap remained lander-only.