Feb 26, 2026·6 min read·34 visits
A logic flaw in the `/v1/rotate` endpoint allows attackers to bypass scope restrictions. By modifying the metadata of a SealedSecret during rotation, an attacker can force the controller to re-encrypt a restricted secret as 'cluster-wide,' enabling them to decrypt it in any namespace.
Bitnami Sealed Secrets, the standard-bearer for GitOps secret management, contains a logic flaw in its key rotation mechanism that allows attackers to widen the scope of encrypted secrets. By injecting malicious metadata during the rotation process, an attacker can transform a strictly scoped secret (bound to a specific namespace) into a cluster-wide secret, subsequently recovering the plaintext credentials. This vulnerability highlights a classic 'Time-of-Check to Time-of-Use' (TOCTOU) style disconnect between the decryption and re-encryption phases.
In the world of Kubernetes GitOps, managing secrets is a headache. You can't commit raw YAMLs to GitHub unless you want to end up on a security researcher's 'Wall of Sheep'. Enter Bitnami Sealed Secrets. It uses asymmetric cryptography to turn your sensitive Secret objects into SealedSecret objects—inert, encrypted blobs that are safe to commit to public repositories. The cluster-side controller holds the private key to decrypt them.
But crypto systems eventually need to rotate keys. Whether it's for hygiene or because an admin leaked a private key on Slack, the sealed-secrets-controller offers a /v1/rotate endpoint. You give it a SealedSecret encrypted with an old key, and it hands you back a SealedSecret encrypted with the new key.
This sounds mundane, but in security, the transition points—where data changes state—are where the bodies are buried. CVE-2026-22728 is a perfect example of what happens when a system validates inputs correctly but fails to carry that validation through to the output.
To understand the bug, you have to understand the anatomy of a SealedSecret. It's essentially a wrapper. The outer object contains metadata (Name, Namespace), and the spec.encryptedData contains the ciphertext. Crucially, the ciphertext is generated based on a 'Scope'.
Scopes dictate who can decrypt the secret:
The controller enforces this during decryption. If you try to decrypt a Strict secret in the wrong namespace, the crypto math fails. However, the Rotate function performs a two-step dance: Decrypt (Unseal) then Re-encrypt (Seal).
The vulnerability lies in the gap between these steps. The controller would successfully decrypt the incoming secret using the valid outer metadata and signature. But when it came time to re-encrypt it for the output, it didn't use the trusted properties it just verified. Instead, it looked at the spec.template.metadata—a field inside the object that the user controls and which is effectively just a blueprint for the final Kubernetes Secret. By manipulating this template before sending it to the rotation endpoint, an attacker can trick the controller into re-sealing the secret with a much wider scope than intended.
Let's look at the Go code responsible for this mix-up. In the vulnerable versions (pre-0.36.0), the Rotate function was too trusting. It took the SealedSecret object, unsealed it to get the plaintext, and then immediately passed that plaintext back into NewSealedSecret to generate the new blob.
The problem? It used the spec.template directly from the input to define the new secret's metadata. Here is the logic flaw in concept:
// VULNERABLE LOGIC (Conceptual)
func Rotate(input SealedSecret) {
// Step 1: Decrypt using Outer Metadata (Verified)
plaintext, err := Unseal(input)
// Step 2: Re-encrypt using Inner Template (UNVERIFIED)
// The attacker changed input.Spec.Template to be ClusterWide!
newSealedSecret := Seal(plaintext, input.Spec.Template)
return newSealedSecret
}The fix, applied in version 0.36.0, forces the application to synchronize its state. It explicitly copies the trusted outer metadata (which was necessary for the successful decryption) into the inner template before re-sealing. This ensures the input scope matches the output scope.
// PATCHED LOGIC
// File: pkg/controller/controller.go
if !reflect.DeepEqual(s.ObjectMeta, s.Spec.Template.ObjectMeta) {
// FORCE the trusted metadata onto the template
s.ObjectMeta.DeepCopyInto(&s.Spec.Template.ObjectMeta)
slog.Warn("Sealed Secret metadata doesn't match... fixed it for you.")
}
secret, err := c.attemptUnseal(s)It's a classic "check your inputs" fix, but applied to the internal state transfer of the application.
An attacker with access to the /v1/rotate endpoint (which typically requires high privileges, hence the CVSS 4.9, but bear with me) can pull off a neat trick. Let's say there is a super sensitive secret db-root-pass that is Strictly scoped to the prod namespace. You, the attacker, want to read it, but you only have access to the dev namespace.
Step 1: Capture the Blob
You grab the SealedSecret YAML for db-root-pass from the public Git repo.
Step 2: Modify the Blueprint
You edit the YAML locally. You leave the encryptedData alone (touching it would break decryption). Instead, you add an annotation to the template:
spec:
template:
metadata:
annotations:
sealedsecrets.bitnami.com/cluster-wide: "true"Step 3: The Rotation Request
You send this modified object to the controller's rotate endpoint. The controller sees the valid encryptedData and valid outer metadata. It decrypts the secret successfully.
Step 4: The Turn The controller now needs to encrypt this plaintext with the new key. It looks at your injected annotation: "Oh, the user wants this to be Cluster Wide? Sure thing." It generates a new blob that can be decrypted anywhere.
Step 5: The Prestige
You take the response (the new SealedSecret), apply it to your dev namespace, and since it is now Cluster Wide, the controller happily decrypts it for you. You now have the production database password.
You might be wondering: "If I can steal production secrets, why is the CVSS score only 4.9?" That feels low for a confidentiality breach of this magnitude.
The devil is in the Privileges Required (PR:H) metric. To communicate with the /v1/rotate endpoint, you usually need permission to POST to the controller's API. In many clusters, this endpoint isn't exposed to the world; it's internal or restricted to admins. If you are already a Cluster Admin, you don't need this exploit—you can just read the secrets directly from etcd.
However, this vulnerability becomes critical in multi-tenant environments with loose RBAC or specific delegation models. If developers are allowed to trigger rotations (perhaps to handle key expiry) but aren't supposed to read other teams' secrets, this is a direct privilege escalation path. It transforms a maintenance function into a data exfiltration tool. It also serves as a warning for anyone building operators: never trust metadata embedded inside a payload if you have a verified source of truth available externally.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
sealed-secrets Bitnami | < 0.36.0 | 0.36.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-668 |
| Attack Vector | Network |
| CVSS | 4.9 (Medium) |
| Privileges Required | High |
| Impact | Confidentiality Loss |
| Exploit Status | PoC Available |
A vulnerability in the Slack and Mattermost platform adapters for NousResearch hermes-agent permits an unauthenticated remote attacker to execute arbitrary mass mentions. By leveraging prompt injection, an attacker can bypass output sanitization logic and trigger workspace-wide notification exhaustion.
CVE-2026-9306 is a critical unauthenticated Insecure Direct Object Reference (IDOR) vulnerability located in the QuantumNous new-api application, affecting versions up to and including 0.12.1. The flaw is caused by improper middleware ordering combined with a lack of object-level authorization checks. This allows remote, unauthenticated attackers to retrieve sensitive Midjourney images belonging to other users by supplying a valid task identifier.
The instagrapi library prior to version 2.6.9 contains an improper input validation vulnerability within its challenge handling mechanism. Maliciously crafted server responses can manipulate the client into forwarding session cookies and credentials to an external attacker-controlled domain.
GHSA-QQQM-5547-774X is a critical path traversal vulnerability in the FileBrowser Quantum application, specifically within the Go backend package. The vulnerability resides in the HTTP handler responsible for processing bulk file modifications via the public API. Unauthenticated attackers can exploit an order-of-operations flaw in the path sanitization logic to bypass intended directory restrictions. This allows adversaries to arbitrarily read, move, and overwrite files on the underlying filesystem by supplying specially crafted HTTP PATCH requests.
The qs query string parsing and serialization library for Node.js is vulnerable to a synchronous Denial of Service (DoS) attack. The vulnerability manifests as a process-terminating TypeError when processing arrays with null or undefined elements under specific configuration parameters.
The aiosend library prior to version 3.0.6 contains a pre-authentication Denial of Service (DoS) vulnerability in its webhook handling mechanism. The software processes and deserializes incoming JSON payloads before verifying the cryptographic signature, allowing unauthenticated attackers to exhaust server CPU and memory resources by sending large, complex payloads.