Overview
CVE-2026-33421 describes a Broken Object Level Authorization issue where Parse Server's LiveQuery WebSocket interface did not enforce Class-Level Permissions (CLP) pointer permissions for readUserFields or pointerFields. Prior to versions 8.6.53 and 9.6.0-alpha.42, any authenticated client could subscribe to LiveQuery events and receive real-time updates for all objects in a class protected by pointer permissions, bypassing the intended read access controlled via the REST API. This could expose sensitive data that should be restricted, especially for objects with private or owner-restricted pointers. The vulnerability is categorized under CWE-863: Incorrect Authorization, and it manifests in real-world Node.js (Express) deployments that rely on Parse Server LiveQuery for real-time data streams without robust server-side access checks.
In practice, an attacker who authenticates to the system and subscribes to a LiveQuery channel for a class could receive all subsequent updates for objects in that class, regardless of whether their pointers reference the subscribing user. Because LiveQuery updates were broadcast to all subscribers without validating per-subscriber permissions, sensitive fields behind pointer relationships could leak to unauthorized clients. The issue specifically affected Parse Server builds before the patched releases: 8.6.53 and 9.6.0-alpha.42, and it is important for Node.js/Express apps integrating LiveQuery to apply the fix or implement equivalent server-side permission checks in their real-time layer.
Mitigation requires not only upgrading to the patched Parse Server versions but also aligning your real-time layer with strict access controls. In Node.js/Express ecosystems, you should ensure that any WebSocket or long-running subscription channel enforces CLP/ACL checks on every published event, and that you do not rely solely on REST API permissions for real-time data. Implement server-side filtering by the authenticated user’s permissions before broadcasting events, and validate permissions consistently across REST and real-time paths. Consider disabling or tightly restricting LiveQuery for sensitive classes until you can guarantee full CLP enforcement.
Affected Versions
Parse Server < 8.6.53 and < 9.6.0-alpha.42
Code Fix Example
Node.js (Express) API Security Remediation
/* Demonstration: Vulnerable vs Fixed LiveQuery-like pattern in Node.js (Express) with WebSocket (ws) */
// Prereqs: npm install express ws
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// In-memory data model (simplified)
const objects = [
{ id: 'o1', className: 'Secret', ownerId: 'u1', pointerFields: ['u1'], data: 'top-secret' },
{ id: 'o2', className: 'Public', ownerId: 'u2', pointerFields: [], data: 'public-info' }
];
// Subscriptions (WebSocket clients)
const subs = [];
// Toggle this to demonstrate vulnerable vs fixed behavior
const VULNERABLE = true;
wss.on('connection', (ws) => {
ws.on('message', (message) => {
try {
const msg = JSON.parse(message);
if (msg.action === 'subscribe') {
// Client indicates their userId and class they want updates for
ws.userId = msg.userId;
ws.className = msg.className;
subs.push(ws);
ws.send(JSON.stringify({ type: 'subscribed', className: ws.className }));
}
} catch (e) {
ws.send(JSON.stringify({ type: 'error', error: 'invalid-message' }));
}
});
});
// Simulate updates to objects over time
setInterval(() => {
const updatedObj = { id: 'o1', className: 'Secret', ownerId: 'u1', pointerFields: ['u1'], data: 'updated' };
if (VULNERABLE) {
// Vulnerable path: broadcast to all subscribers regardless of per-user CLP/permissions
subs.forEach((s) => {
if (s.readyState === WebSocket.OPEN) {
s.send(JSON.stringify({ type: 'update', object: updatedObj }));
}
});
} else {
// Fixed path: enforce per-subscriber permission checks before broadcasting
subs.forEach((s) => {
if (s.readyState !== WebSocket.OPEN) return;
if (canRead(s.userId, updatedObj)) {
s.send(JSON.stringify({ type: 'update', object: updatedObj }));
}
});
}
}, 3000);
// Minimal permission check (server-side CLP/ACL enforcement)
function canRead(userId, obj) {
// Simplified: allow if user is the owner; in real apps use CLP/ACL rules
if (!userId) return false;
return obj.ownerId === userId;
}
server.listen(3000, () => {
console.log('LiveQuery-like server listening on port 3000');
});
/*
Vulnerable pattern summary:
- ws broadcasts object updates to all subscribers regardless of pointer permissions.
- A client subscribed to a class with restricted pointers can receive updates for all objects, enabling BOLOA-like leakage.
Fixed pattern summary:
- Before broadcasting, for each subscriber, verify canRead(sub.userId, object) and only send if permitted.
- Replace the placeholder canRead with your CLP/ACL implementation tying to your identity/auth model.
*/