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-25958
7.7

The Cube Root of Chaos: Smuggling Admin Privileges via WebSocket Pollution

Alon Barad
Alon Barad
Software Engineer

Feb 10, 2026·6 min read·8 visits

PoC Available

Executive Summary (TL;DR)

An authenticated attacker can escalate privileges by injecting a `securityContext` object into WebSocket messages or triggering a race condition in the API gateway. This allows bypassing Row-Level Security (RLS) and multi-tenant isolation, granting full admin access or data visibility across tenants.

Cube (formerly Cube.js) is the self-proclaimed 'semantic layer' for building data applications—a fancy way of saying it sits between your messy SQL databases and your pretty frontend charts. In versions prior to 1.5.13, Cube suffered from a catastrophic logic error in its WebSocket gateway and async handling. By simply asking nicely (read: injecting a JSON object), a low-privileged user could override their own security context. Even worse, a race condition in the promise chain allowed user contexts to 'bleed' into one another, meaning your request for 'My Sales' might accidentally return 'competitor_sales' if the server was busy enough. This is a classic case of trusting client input combined with the distinct misery of Node.js event loop management.

The Hook: The Semantic Layer Cake

In modern data stacks, 'Semantic Layers' are the new hotness. They act as the single source of truth, translating abstract business logic (like 'Revenue') into concrete SQL queries. Cube is the leader in this space. It handles authentication, caching, and query orchestration. Ideally, it's the bouncer at the club, checking IDs and ensuring you only sit at the table you paid for.

But here's the thing about bouncers: if they are distracted or easily bribed, the whole security model collapses. Cube relies heavily on 'Security Contexts'—objects defining who you are and what you can see (e.g., tenant_id: 123). This context is usually derived from a cryptographically signed JWT. You trust the JWT, you trust the context.

CVE-2026-25958 reveals that Cube didn't just trust the JWT. It also trusted the WebSocket packet you just sent it. It’s the equivalent of the bouncer checking your ID, then asking, 'And what VIP level would you like to be today?' and actually writing it down on your wristband.

The Flaw: Parameter Pollution & Broken Promises

This vulnerability is a two-headed beast. The first head is WebSocket Parameter Pollution. When you connect to Cube via WebSockets, you send JSON messages to trigger data loads. The backend logic took the user's provided message and merged it into the internal state. The developers failed to sanitize the input for reserved keys like securityContext.

If you sent a payload containing securityContext: { isAdmin: true }, the application effectively said, 'Sure, seems legit,' and merged that flag into your active session. This allows for trivial privilege escalation. You aren't cracking a password; you're just politely informing the server that you are, in fact, the boss.

The second head is a Race Condition in the Async Boundary. In packages/cubejs-api-gateway/src/gateway.ts, specific core methods (like meta and sql) invoked response callbacks without properly awaiting the result. In the Node.js event loop, this created a 'broken promise chain'.

Because the request context wasn't strictly bound to the execution scope of the database query, high-concurrency traffic could cause Context Bleeding. The security context of User A (an Admin) could leak into the response processing of User B (a Viewer), accidentally serving privileged data to the wrong socket.

The Code: The Smoking Gun

Let's look at the failure in the WebSocket handler. The code was iterating over the message parameters and blindly applying them. It lacked a 'strict' schema validation.

The Vulnerable Pattern (Simplified):

// Inside the WebSocket message handler
const processMessage = (message, connection) => {
  // message.params comes directly from the user
  const queryContext = {
    ...connection.userContext, // The real context from JWT
    ...message.params          // The untrusted input overwriting it
  };
  
  // queryContext now has user-injected 'isAdmin: true'
  return executeQuery(queryContext);
};

The Fix (Commit 6271520): The patch introduced zod schemas to strictly validate inputs. Instead of a spread operator that merges everything, the new code explicitly parses only allowed fields.

// The Fix: Strict Schema Validation
const LoadRequestSchema = z.object({
  query: QuerySchema,
  queryType: z.string().optional(),
  // securityContext is notably ABSENT from the allowed schema
});
 
const safeParams = LoadRequestSchema.parse(message.params);
// Now safeParams only contains what the devs intended.

Additionally, in gateway.ts (Commit 9e9f9d1), they had to fix the async bleeding.

Before:

this.loadImpl(options, (err, res) => {
   // Callback hell leading to context loss
});

After:

// Forcing the promise to resolve within the correct context scope
await this.loadImpl(options);

By forcing the await, the AsyncLocalStorage context remains consistent throughout the lifecycle of the request.

The Exploit: Becoming the Admin

To exploit this, you don't need fancy binary tools. You just need a WebSocket client and a valid low-privileged JWT. The goal is to bypass the Row-Level Security (RLS) that keeps tenants separate.

Step 1: Connect Establish a standard WebSocket connection to the Cube API. Authenticate with your low-privilege JWT.

Step 2: Craft the Payload Construct a load message. The documentation says you should send a query. The exploit says you should send a query AND a securityContext.

{
  "messageId": "1337",
  "method": "load",
  "params": {
    "query": {
      "measures": ["Orders.totalRevenue"],
      "dimensions": ["Orders.createdAt"]
    },
    "securityContext": {
      "u": {
        "id": 1,
        "roles": ["admin"],
        "tenantId": "*" 
      }
    }
  }
}

Step 3: Execution Send the JSON. The server deserializes the message. The securityContext provided in params overrides the one extracted from your JWT. Cube generates the SQL. Instead of WHERE tenant_id = 'your_id', it generates WHERE 1=1 (or whatever the admin rule is).

Step 4: Profit The server streams back the financial data for every tenant on the platform. You have successfully democratized the data, whether they wanted you to or not.

The Impact: Data Democratization (Involuntary)

This is a Critical Severity issue for any multi-tenant SaaS application using Cube. The impact is not just data leakage; it's total isolation failure.

  1. Row-Level Security Bypass: RLS is the only thing stopping Tenant A from seeing Tenant B's data. This exploit deletes that barrier.
  2. Privilege Escalation: If the application uses Cube's security context to determine UI features or write permissions (less common in Cube, but possible), an attacker gains administrative control.
  3. Metadata Injection: In some scenarios involving the order parameter (CWE-89), this could escalate further into blind SQL injection via the metadata generation layer.

The business impact? If you are a B2B SaaS provider, your customers' competitors just got access to their sales numbers. Trust is the first casualty.

The Fix: Closing the Window

Remediation is straightforward but urgent. You must upgrade the Cube core packages.

Action Items:

  1. Upgrade: Move to version 1.5.13, 1.4.2, or 1.0.14 immediately. These versions introduce the Zod schema validation that strips the malicious payload.
  2. Audit Logs: Check your WebSocket logs (if available) for requests containing the securityContext key in the parameters. This key should effectively never come from the client side.
  3. Disable WebSockets (Optional): If you aren't using real-time dashboards, turn them off via CUBEJS_WEB_SOCKETS=false. This reduces the attack surface significantly, as the REST API uses a different parsing path that was less susceptible to this specific pollution attack.

Developer Lesson: Never use the spread operator (...) on user input when building security contexts. It's concise, it's pretty, and it's dangerous.

Official Patches

CubePatch for WebSocket sanitization
CubePatch for Async Context Bleeding

Fix Analysis (2)

Technical Appendix

CVSS Score
7.7/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N

Affected Systems

Cube.js API GatewayCube Server CoreNode.js Semantic Layer implementations

Affected Versions Detail

Product
Affected Versions
Fixed Version
Cube
Cube Dev
0.27.19 - < 1.0.141.0.14
Cube
Cube Dev
1.1.0 - < 1.4.21.4.2
Cube
Cube Dev
1.5.0 - < 1.5.131.5.13
AttributeDetail
CWE IDCWE-807
Attack VectorNetwork (WebSocket)
CVSS7.7 (High)
ImpactPrivilege Escalation / Info Disclosure
Exploit StatusPoC Available
Fix ComplexityLow (Version Upgrade)

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1078Valid Accounts
Defense Evasion
T1068Exploitation for Privilege Escalation
Privilege Escalation
CWE-807
Reliance on Untrusted Inputs in a Security Decision

The application relies on untrusted inputs to make security decisions, specifically allowing client-side overriding of the security context.

Known Exploits & Detection

NVDAdvisory details indicating WebSocket parameter pollution.
GitHub Security AdvisoryTechnical description of the context bleeding and pollution vectors.

Vulnerability Timeline

Internal Fix for Native Layer Isolation
2025-12-04
Fix for WebSocket Sanitization
2025-12-09
Version 1.5.13 Released
2025-12-10
CVE-2026-25958 Published
2026-02-09

References & Sources

  • [1]GHSA Advisory
  • [2]NVD CVE-2026-25958

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.