Bitnami's Sealed Secrets exists to solve exactly one problem: let a team commit an encrypted secret to Git without leaking the plaintext, and let them scope that encryption so a secret sealed for one namespace can't be unsealed anywhere else. CVE-2026-22728, disclosed in February 2026, broke the second half of that promise — and it broke it through the one operation every rotation-conscious team was already running routinely: key rotation.
The bug (CVSS 4.9, fixed in v0.36.0) sat in the controller's /v1/rotate endpoint, the same code path kubeseal --re-encrypt calls to re-seal an existing secret under the cluster's current key. Submit a victim's namespace-scoped or strict-scoped SealedSecret to that endpoint with one forged annotation — sealedsecrets.bitnami.com/cluster-wide=true — added to the input's template metadata, and what came back was a cluster-wide-sealed version of the same secret. From there, an attacker anywhere in the cluster could unseal it under any name, in any namespace, and read the plaintext. No RBAC escalation, no admission-controller bypass, no CVE in Kubernetes itself — just a re-encryption endpoint that trusted the wrong input.
The Three Scopes Sealed Secrets Was Supposed to Enforce
Sealed Secrets ships three sealing scopes, and the whole security model rests on which one a secret was sealed under:
- Strict (the default): the sealed blob is bound to the secret's exact name and namespace. Change either and it won't decrypt.
- Namespace-wide: bound to the namespace only — any secret name within that namespace can unseal it.
- Cluster-wide: bound to nothing. Any name, any namespace, anywhere in the cluster, can unseal it.
A team sealing a database password for payments-prod under strict scope is making a specific claim: this ciphertext is worthless to anything except that exact Secret object, in that exact namespace. That's the guarantee a multi-tenant platform leans on when it tells itself "our GitOps secrets are namespace-isolated" — the scope encoded at seal time is supposed to be the only thing that determines what can unseal it later.
Where the Rotation Handler Broke That Guarantee
Rotation is the one place a SealedSecret's ciphertext legitimately needs to change without a human resealing it from scratch — the cluster's sealing key rotates (30 days by default), and every existing sealed secret needs to be re-encrypted under the new key or it becomes unreadable. kubeseal --rotate calls the controller's /v1/rotate endpoint to do exactly that: decrypt the old blob, re-seal the plaintext under the current key, hand back a fresh SealedSecret object.
The vulnerable code's mistake was where it read the scope for that fresh object from. Instead of deriving it from the original SealedSecret's own encrypted scope, the handler pulled it from spec.template.metadata.annotations on the input object — a field an attacker fully controls, since it's just YAML in the SealedSecret they're submitting for rotation. Decrypt, then re-seal using whatever scope annotation showed up on the request: that's a rotation endpoint trusting attacker-supplied metadata to decide the security property of its own output.
Concretely, the attack is a one-line YAML edit. Take a victim's existing strict-scoped SealedSecret (publicly visible in a Git repo, or read off the cluster by anyone with get on that one object), add a single annotation to its template metadata:
spec:
template:
metadata:
annotations:
sealedsecrets.bitnami.com/cluster-wide: "true"— then submit that modified manifest to /v1/rotate. The endpoint decrypts the original ciphertext using the controller's private key (which it has to do anyway, to re-seal under the current key), sees the forged annotation, and re-seals the plaintext with cluster-wide scope instead of the strict scope the victim actually set. The response is a new SealedSecret an attacker can immediately unseal under any name, in any namespace — no further access to the victim's original object required, no Kubernetes RBAC grant needed on the Secret itself. The bug isn't in the crypto (the encryption is still sound); it's in which unauthenticated field the handler let decide the output's authorization scope.
Why the Blast Radius Is Bigger Than One Project's Bug Tracker
Sealed Secrets isn't a niche tool. The project sits around 9,000+ GitHub stars and shows up repeatedly in 2026 comparisons of GitOps secrets tooling as the default answer to one narrow, common problem: "we do GitOps, so our whole desired state lives in Git, but secrets can't go in Git in plaintext." That framing is exactly why it ends up wired into so many clusters that also happen to run multiple tenants or applications side by side on shared infrastructure — it's small, it's easy to adopt incrementally per-namespace, and adopting it doesn't require standing up a separate secrets-management platform like Vault or External Secrets Operator's cloud-KMS integrations. The same simplicity that makes it a five-minute install is what makes a controller-level bug in it a cross-tenant problem the moment more than one team shares the cluster it's running on.
The Part That Should Worry a Self-Hosted PaaS More Than the CVSS Score Suggests
CVSS 4.9 reads as "medium, don't panic." What that score doesn't capture is what kind of access the attack actually requires — and it's not what most teams assume.
This isn't a Kubernetes RBAC bypass. The attacker never needs get/create permissions on Secret objects, never touches the Kubernetes API server's authorization path at all. /v1/rotate is an HTTP endpoint on the Sealed Secrets controller's own Service — the same one kubeseal --rotate reaches via kubectl port-forwarding or in-cluster DNS. Anything with network reach to that Service's ClusterIP can call it directly, no kubectl credentials required. In a cleanly namespaced, single-tenant cluster, "network reach to the controller Service" is already a fairly privileged position. In a shared, multi-tenant cluster — the exact topology a self-hosted PaaS runs when it puts unrelated tenants' workloads on the same Kubernetes cluster to amortize infrastructure cost — that Service usually sits reachable from every tenant namespace by default, because nobody thought to write a NetworkPolicy scoping controller plumbing. Teams write NetworkPolicies to isolate tenant workloads from each other; they don't think to isolate tenant workloads from the shared cluster's own secrets-management controller, because "call the sealed-secrets controller" doesn't look like an attack surface. It looks like infrastructure.
That's the actual lesson CVE-2026-22728 teaches: a compromised or malicious workload in any tenant namespace, with nothing more than default pod-to-pod network access, could reach across every tenant boundary the RBAC layer enforces and pull a different tenant's plaintext secret out through a rotation call — a lateral-movement path that bypasses the isolation mechanism operators actually audited (namespace-scoped RBAC) because it never goes through the API server RBAC checks at all.
The Concrete Audit Checklist
If you're running Sealed Secrets anywhere near a shared or multi-tenant cluster, four things are worth checking today, not at the next maintenance window:
- Version.
kubectl get deployment sealed-secrets-controller -n kube-system -o jsonpath='{.spec.template.spec.containers[0].image}'(adjust namespace/name to your install) and confirm it'sv0.36.0or later. Every version before that is vulnerable, full stop. - NetworkPolicy on the controller Service. Sealed Secrets' controller Service should not be reachable from arbitrary tenant namespaces. Scope ingress to the specific namespaces or service accounts (typically CI/CD or a platform-operator namespace) that legitimately need to call
kubeseal --sealor--rotateagainst it — the same default-deny-then-allowlist posture you'd apply to the Kubernetes API server itself, not the looser "internal plumbing, don't bother" posture most clusters give it today. - Don't equate namespace-scoped RBAC with tenant isolation for anything that has its own separate HTTP surface. Sealed Secrets, admission webhooks, GitOps controllers with reconciliation APIs — any component with a Service endpoint distinct from the Kubernetes API server is a second authorization boundary that RBAC doesn't automatically cover. If it takes input from a tenant-writable object (a SealedSecret, a CRD spec) and produces a security-relevant output, that input needs the same "assume it's hostile" treatment the Kubernetes API server itself gets.
- Consider whether a shared controller is the right architecture at all. For a platform running truly untrusted, mutually adversarial tenants on one cluster, a single shared Sealed Secrets controller — however well NetworkPolicy-fenced — is one more component whose compromise is total. A per-tenant controller instance, or a move to External Secrets Operator (which syncs from an external secrets store the platform controls rather than decrypting attacker-influenceable ciphertext in-cluster), removes an entire class of "the shared re-encryption endpoint got tricked" bugs by not having a shared re-encryption endpoint in the first place.
Isolation Claims Need a Blast-Radius Audit, Not Just a Version Check
None of this makes Sealed Secrets a bad tool — the fix shipped, the maintainers scoped the CVE precisely, and the design (encrypt client-side, decrypt only in-cluster) is still sound. What it changes is how much weight a platform operator can put on "we use namespace-scoped GitOps secrets, so tenants are isolated" without having actually traced the request path a rotation call takes. The isolation guarantee a tool advertises and the isolation guarantee its network topology actually enforces are two different claims, and CVE-2026-22728 is a clean example of the gap between them: the scope was cryptographically correct at seal time and silently overridable at rotation time, through a code path nobody thought to threat-model because it "just does housekeeping."
A self-hosted PaaS that runs multiple tenants' workloads on shared infrastructure inherits this exact class of risk for every controller in its cluster with its own HTTP surface — and the fix isn't a bigger secrets vendor, it's actually drawing the NetworkPolicy boundary around every one of those controllers the way you'd draw it around the Kubernetes API server itself.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with the isolation boundaries between tenants drawn deliberately rather than inherited by default. Star the repo on GitHub or deploy your first app today.



