CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-23990
5.30.03%

The Invisible User: Escaping RBAC in Flux Operator via OIDC Null-States

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 15, 2026·8 min read·2 visits

PoC Available

Executive Summary (TL;DR)

The Flux Operator Web UI fails to validate empty OIDC claims. If a user's identity maps to an empty string, the backend skips adding impersonation headers to Kubernetes API requests. Consequently, the request falls back to using the Operator's own Service Account (usually cluster-admin), allowing any authenticated user to escalate privileges and take over the cluster.

A critical logic flaw in the Flux Operator Web UI's OIDC handling allows authenticated users to bypass Kubernetes impersonation mechanisms. By manipulating OIDC claims to resolve to empty values, attackers can force the Operator to execute API requests using its own high-privileged Service Account rather than the user's restricted credentials, effectively granting full cluster administrative access.

The Setup: A Wolf in Sheep's Clothing

Kubernetes operators are the unsung heroes of modern infrastructure, automating the tedious tasks that would otherwise drive SysAdmins to drink. The Flux Operator is particularly nifty: it provides a slick Web UI to manage Flux CD, giving developers a window into their GitOps pipelines. To do this securely, it employs a technique called User Impersonation.

Here is how it should work: You log in via OIDC (say, Google or GitHub). The Operator takes your token, verifies who you are, and then says to the Kubernetes API, "Hey, I'm the Operator, but please execute this next command as Bob from Accounting." This is handled via Impersonate-User headers. The Kubernetes API checks if Bob has permission to delete the production database (he shouldn't), and allows or denies the request accordingly. The Operator is just the messenger.

But in security, the messenger is often the most dangerous link in the chain. This vulnerability, CVE-2026-23990, exists in the handshake between the Operator's authentication logic and the Kubernetes client library. It turns out, if you whisper your name quietly enough (or don't say it at all), the Operator forgets to tell Kubernetes who you are. And when the Operator forgets to impersonate you, it accidentally acts as itself—a Service Account with god-mode privileges.

The Flaw: The Sound of Silence

The root cause here is a classic "fail-open" scenario born from a misunderstanding of the underlying client-go library. The Flux Operator allows administrators to define CEL (Common Expression Language) expressions to map OIDC claims (like email or groups) to Kubernetes identities. For example, claim.email becomes the Kubernetes username.

But what happens if that expression resolves to an empty string? Perhaps the OIDC provider didn't send an email claim, or the CEL expression was malformed. The code handled this by creating an internal Impersonation object where Username was "" and Groups were [].

Here is the fatal flaw: The developer assumed that an empty impersonation config would result in an error or a harmless no-op. Instead, they handed this empty configuration to the Kubernetes client-go transport wrapper.

When client-go sees an impersonation config, it checks: "Is there a username?" If yes, it adds the Impersonate-User header. If no, it simply doesn't add the header. It doesn't throw an error; it just sends a standard request. Since the Operator runs inside the cluster, it uses its own Service Account token for authentication. Without the impersonation header acting as a filter, the Kubernetes API treats the request as coming directly from the Flux Operator Service Account.

It is the digital equivalent of a bouncer checking your ID. If you show a fake ID, he kicks you out. But if you show him nothing, he apparently assumes you own the club and lets you into the VIP room.

The Code: Autopsy of a Null Pointer Logic

Let's look at the "smoking gun" in the code. Prior to version 0.40.0, the authentication flow extracted claims and happily passed them along without ensuring they actually contained data. The logic resided in how the Impersonation struct was populated and subsequently used.

The vulnerability existed because there was no gatekeeper ensuring len(Username) > 0. The fix, introduced in commit 0845404, adds a specific SanitizeAndValidate method that is called immediately after claim extraction.

Here is the breakdown of the fix:

// internal/web/user/user.go
 
func (imp *Impersonation) SanitizeAndValidate() error {
    // Trim spaces to prevent " " from bypassing empty checks
    imp.Username = strings.TrimSpace(imp.Username)
    for i, g := range imp.Groups {
        imp.Groups[i] = strings.TrimSpace(g)
    }
 
    // THE FIX: Explicitly forbid the "invisible man" scenario
    if imp.Username == "" && len(imp.Groups) == 0 {
        return fmt.Errorf("at least one of 'username' or 'groups' must be set for user impersonation")
    }
 
    // ... additional validation ...
    return nil
}

Before this patch, the code would simply proceed. If Username was empty, the transport layer in client-go would construct the HTTP request without the Impersonate-User header. The request would look like this on the wire:

Vulnerable Request (Effective Admin):

GET /api/v1/pods HTTP/1.1
Authorization: Bearer <OPERATOR_SERVICE_ACCOUNT_TOKEN>
Accept: application/json

Intended Request (Restricted):

GET /api/v1/pods HTTP/1.1
Authorization: Bearer <OPERATOR_SERVICE_ACCOUNT_TOKEN>
Impersonate-User: bob@example.com
Accept: application/json

By ensuring the Impersonation struct is never empty, the patch forces the second path, or errors out before the network call is ever made.

The Exploit: Becoming the System

To exploit this, we don't need buffer overflows or heap spraying. We just need to be a "nobody." The goal is to authenticate against the Flux Operator Web UI but ensure our resulting identity claims are empty.

Attack Scenario

  1. Reconnaissance: The attacker identifies a Flux Operator Web UI exposed to the network. They have a valid login (perhaps they are a low-level developer or a contractor).
  2. Configuration Analysis: The attacker creates an OIDC token state where the mapped claims are missing. For example, if the Operator is configured to map preferred_username to the K8s user, the attacker might modify their OIDC profile (if they have control over the IdP) or use an IdP that omits this claim for their specific user scope.
  3. The Login: The attacker logs into the Flux UI. The OIDC flow completes successfully (authentication works). The ClaimsProcessor runs the CEL expression.
    • Expression: claim.preferred_username
    • Input: {"sub": "123", "email": "attacker@evil.com"} (Note: missing preferred_username)
    • Result: "" (Empty String)
  4. The Action: The attacker navigates to the "Cluster Resources" page. The UI requests a list of Secrets.
  5. The Bypass: The backend constructs the Kubernetes client with an empty Impersonation config. The request goes out signed by the Operator's Service Account.
  6. Success: The Kubernetes API sees a request from system:serviceaccount:flux-system:flux-operator. Since this account needs to manage the entire Flux lifecycle, it has permissions to read Secrets, create Deployments, or delete Namespaces. The attacker receives the JSON response containing the cluster's secrets.

This is a clean, logic-based privilege escalation. No crashing services, no noisy binaries—just one "empty" string turning a guest into a god.

The Impact: Why This Matters

The severity of this vulnerability is deceptive. The CVSS score sits at a "Medium" 5.3, mostly because it requires a specific configuration (OIDC claims resulting in empty values) and authenticated access. However, in the environments where it is exploitable, the impact is catastrophic.

The Flux Operator is not a lowly web app; it is a control plane component. By design, its Service Account requires extensive permissions—often cluster-admin or near-equivalent roles—to reconcile arbitrary Kubernetes manifests defined in Git.

If an attacker exploits this:

  • Data Exfiltration: They can read every Secret and ConfigMap in the cluster, compromising database credentials, API keys, and cloud provider tokens.
  • Cluster Takeover: They can deploy malicious workloads (crypto miners, backdoors) by creating new Deployments acting as the Operator.
  • Destruction: They can delete critical namespaces, effectively wiping the cluster.

Because the vulnerability exploits the lack of an identity, audit logs will likely show the actions as being performed by the flux-operator service account, making attribution difficult. The security team will see the "system" doing system things, while the attacker sits quietly behind the web interface.

Remediation: Patching the Void

The path to safety is straightforward, but urgency is key. If you are running flux-operator versions 0.36.0 through 0.39.0, you are vulnerable.

Immediate Steps

  1. Upgrade: Update the Flux Operator to v0.40.0 or later immediately. The patch logic is robust and explicitly prevents empty impersonation contexts.
  2. Audit CEL Expressions: Review your FluxOperator resource configuration. Look at the authentication.oidc.claims section. Ensure that your CEL expressions have fallbacks.
    • Bad: claim.preferred_username
    • Better: has(claim.preferred_username) ? claim.preferred_username : claim.email
    • Even Better: Ensure your IdP enforces the presence of these claims.

Detection

Since this exploit abuses legitimate Service Account tokens, network-level detection is hard. Focus on Kubernetes Audit Logs. Look for an anomaly in the behavior of the flux-operator Service Account. If the Service Account is making API calls that correlate exactly with user HTTP sessions on the Flux UI, but are not impersonated requests, that is a red flag.

Normally, the Operator's automated reconciliation loop is periodic and predictable. User-driven actions (via this exploit) will be sporadic and might access resources the reconciliation loop typically ignores (like reading specific Secrets outside of managed namespaces).

Official Patches

ControlPlaneOfficial Release v0.40.0 containing the fix

Fix Analysis (1)

Technical Appendix

CVSS Score
5.3/ 10
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
EPSS Probability
0.03%
Top 90% most exploited

Affected Systems

Flux Operator Web UI

Affected Versions Detail

Product
Affected Versions
Fixed Version
flux-operator
controlplaneio-fluxcd
>= 0.36.0, < 0.40.00.40.0
AttributeDetail
CWECWE-269 (Improper Privilege Management)
CVSS v3.15.3 (Medium)
Attack VectorNetwork (Authenticated)
Attack ComplexityHigh (Requires specific OIDC/CEL state)
Exploit MaturityPoC Available
ImpactFull Cluster Privilege Escalation

MITRE ATT&CK Mapping

T1068Exploitation for Privilege Escalation
Privilege Escalation
T1078Valid Accounts
Initial Access
CWE-269
Improper Privilege Management

The application does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.

Known Exploits & Detection

Internal ResearchThe advisory details the lack of validation in claims processing.

Vulnerability Timeline

Fix commit merged to main branch
2026-01-20
CVE-2026-23990 Published
2026-01-21
SentinelOne analysis released
2026-01-23

References & Sources

  • [1]GHSA Advisory
  • [2]CISA Bulletin SB26-026

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.