CVE-2026-34595
Description
Parse Server is an open source backend that can be deployed to any infrastructure that can run Node.js. Prior to versions 8.6.70 and 9.7.0-alpha.18, an authenticated user with find class-level permission can bypass the protectedFields class-level permission setting on LiveQuery subscriptions. By sending a subscription with a $or, $and, or $nor operator value as a plain object with numeric keys and a length property (an "array-like" object) instead of an array, the protected-field guard is bypassed. The subscription event firing acts as a binary oracle, allowing the attacker to infer whether a protected field matches a given test value. This issue has been patched in versions 8.6.70 and 9.7.0-alpha.18.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
parse-servernpm | >= 9.0.0, < 9.7.0-alpha.16 | 9.7.0-alpha.16 |
parse-servernpm | < 8.6.70 | 8.6.70 |
Affected products
16cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha10:*:*:*:node.js:*:*+ 15 more
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha10:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha11:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha12:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha13:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha14:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha15:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha1:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha2:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha3:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha4:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha5:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha6:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha7:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha8:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha9:*:*:*:node.js:*:*
- cpe:2.3:a:parseplatform:parse-server:*:*:*:*:*:node.js:*:*range: <8.6.70
Patches
2ffad0ec6b971fix: LiveQuery protected-field guard bypass via array-like logical operator value ([GHSA-mmg8-87c5-jrc2](https://github.com/parse-community/parse-server/security/advisories/GHSA-mmg8-87c5-jrc2)) (#10351)
4 files changed · +3589 −3449
spec/vulnerabilities.spec.js+3557 −3437 modifiedsrc/LiveQuery/ParseLiveQueryServer.ts+16 −12 modified@@ -555,6 +555,16 @@ class ParseLiveQueryServer { if (typeof where !== 'object' || where === null) { return; } + for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, `${op} must be an array`); + } + if (Array.isArray(where[op])) { + where[op].forEach((subQuery: any) => { + this._validateQueryConstraints(subQuery); + }); + } + } for (const key of Object.keys(where)) { const constraint = where[key]; if (typeof constraint === 'object' && constraint !== null) { @@ -582,18 +592,6 @@ class ParseLiveQueryServer { ); } } - for (const op of ['$or', '$and', '$nor']) { - if (Array.isArray(constraint[op])) { - constraint[op].forEach((subQuery: any) => { - this._validateQueryConstraints(subQuery); - }); - } - } - if (Array.isArray(where[key])) { - where[key].forEach((subQuery: any) => { - this._validateQueryConstraints(subQuery); - }); - } } } } @@ -1048,6 +1046,9 @@ class ParseLiveQueryServer { return; } for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, `${op} must be an array`); + } if (Array.isArray(where[op])) { for (const subQuery of where[op]) { checkDepth(subQuery, depth + 1); @@ -1111,6 +1112,9 @@ class ParseLiveQueryServer { } } for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, `${op} must be an array`); + } if (Array.isArray(where[op])) { where[op].forEach((subQuery: any) => checkWhere(subQuery)); }
src/LiveQuery/QueryTools.js+9 −0 modified@@ -206,6 +206,9 @@ function matchesKeyConstraints(object, key, constraints) { } var i; if (key === '$or') { + if (!Array.isArray(constraints)) { + return false; + } for (i = 0; i < constraints.length; i++) { if (matchesQuery(object, constraints[i])) { return true; @@ -214,6 +217,9 @@ function matchesKeyConstraints(object, key, constraints) { return false; } if (key === '$and') { + if (!Array.isArray(constraints)) { + return false; + } for (i = 0; i < constraints.length; i++) { if (!matchesQuery(object, constraints[i])) { return false; @@ -222,6 +228,9 @@ function matchesKeyConstraints(object, key, constraints) { return true; } if (key === '$nor') { + if (!Array.isArray(constraints)) { + return false; + } for (i = 0; i < constraints.length; i++) { if (matchesQuery(object, constraints[i])) { return false;
src/RestQuery.js+7 −0 modified@@ -904,6 +904,13 @@ _UnsafeRestQuery.prototype.denyProtectedFields = async function () { } } for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw createSanitizedError( + Parse.Error.INVALID_QUERY, + `${op} must be an array`, + this.config + ); + } if (Array.isArray(where[op])) { where[op].forEach(subQuery => checkWhere(subQuery)); }
f63fd1a3fe0afix: LiveQuery protected-field guard bypass via array-like logical operator value ([GHSA-mmg8-87c5-jrc2](https://github.com/parse-community/parse-server/security/advisories/GHSA-mmg8-87c5-jrc2)) (#10350)
4 files changed · +3867 −3727
spec/vulnerabilities.spec.js+3835 −3715 modifiedsrc/LiveQuery/ParseLiveQueryServer.ts+16 −12 modified@@ -555,6 +555,16 @@ class ParseLiveQueryServer { if (typeof where !== 'object' || where === null) { return; } + for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, `${op} must be an array`); + } + if (Array.isArray(where[op])) { + where[op].forEach((subQuery: any) => { + this._validateQueryConstraints(subQuery); + }); + } + } for (const key of Object.keys(where)) { const constraint = where[key]; if (typeof constraint === 'object' && constraint !== null) { @@ -582,18 +592,6 @@ class ParseLiveQueryServer { ); } } - for (const op of ['$or', '$and', '$nor']) { - if (Array.isArray(constraint[op])) { - constraint[op].forEach((subQuery: any) => { - this._validateQueryConstraints(subQuery); - }); - } - } - if (Array.isArray(where[key])) { - where[key].forEach((subQuery: any) => { - this._validateQueryConstraints(subQuery); - }); - } } } } @@ -1048,6 +1046,9 @@ class ParseLiveQueryServer { return; } for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, `${op} must be an array`); + } if (Array.isArray(where[op])) { for (const subQuery of where[op]) { checkDepth(subQuery, depth + 1); @@ -1111,6 +1112,9 @@ class ParseLiveQueryServer { } } for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, `${op} must be an array`); + } if (Array.isArray(where[op])) { where[op].forEach((subQuery: any) => checkWhere(subQuery)); }
src/LiveQuery/QueryTools.js+9 −0 modified@@ -213,6 +213,9 @@ function matchesKeyConstraints(object, key, constraints) { } var i; if (key === '$or') { + if (!Array.isArray(constraints)) { + return false; + } for (i = 0; i < constraints.length; i++) { if (matchesQuery(object, constraints[i])) { return true; @@ -221,6 +224,9 @@ function matchesKeyConstraints(object, key, constraints) { return false; } if (key === '$and') { + if (!Array.isArray(constraints)) { + return false; + } for (i = 0; i < constraints.length; i++) { if (!matchesQuery(object, constraints[i])) { return false; @@ -229,6 +235,9 @@ function matchesKeyConstraints(object, key, constraints) { return true; } if (key === '$nor') { + if (!Array.isArray(constraints)) { + return false; + } for (i = 0; i < constraints.length; i++) { if (matchesQuery(object, constraints[i])) { return false;
src/RestQuery.js+7 −0 modified@@ -924,6 +924,13 @@ _UnsafeRestQuery.prototype.denyProtectedFields = async function () { } } for (const op of ['$or', '$and', '$nor']) { + if (where[op] !== undefined && !Array.isArray(where[op])) { + throw createSanitizedError( + Parse.Error.INVALID_QUERY, + `${op} must be an array`, + this.config + ); + } if (Array.isArray(where[op])) { where[op].forEach(subQuery => checkWhere(subQuery)); }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/parse-community/parse-server/commit/f63fd1a3fe0a7c1c5fe809f01b0e04759e8c9b98nvdPatchWEB
- github.com/parse-community/parse-server/commit/ffad0ec6b971ee0dd9545e1bf1fb34ddebf275c2nvdPatchWEB
- github.com/parse-community/parse-server/pull/10350nvdIssue TrackingPatchWEB
- github.com/parse-community/parse-server/pull/10351nvdIssue TrackingPatchWEB
- github.com/parse-community/parse-server/security/advisories/GHSA-mmg8-87c5-jrc2nvdPatchVendor AdvisoryWEB
- github.com/advisories/GHSA-mmg8-87c5-jrc2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-34595ghsaADVISORY
News mentions
0No linked articles in our index yet.