Parse Server: Query condition depth bypass via pre-validation transform pipeline
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.55 and 9.6.0-alpha.44, an attacker can send an unauthenticated HTTP request with a deeply nested query containing logical operators to permanently hang the Parse Server process. The server becomes completely unresponsive and must be manually restarted. This is a bypass of the fix for CVE-2026-32944. This issue has been patched in versions 8.6.55 and 9.6.0-alpha.44.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
parse-servernpm | >= 9.0.0, < 9.6.0-alpha.44 | 9.6.0-alpha.44 |
parse-servernpm | < 8.6.55 | 8.6.55 |
Affected products
1- Range: < 8.6.55
Patches
285994eff9e7bfix: Query condition depth bypass via pre-validation transform pipeline ([GHSA-9fjp-q3c4-6w3j](https://github.com/parse-community/parse-server/security/advisories/GHSA-9fjp-q3c4-6w3j)) (#10257)
2 files changed · +117 −0
spec/vulnerabilities.spec.js+84 −0 modified@@ -2833,6 +2833,90 @@ describe('(GHSA-9xp9-j92r-p88v) Stack overflow process crash via deeply nested q }) ); }); + + it('rejects deeply nested query before transform pipeline processes it', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + const auth = require('../lib/Auth'); + const rest = require('../lib/rest'); + const config = Config.get('test'); + // Depth 50 bypasses the fix because RestQuery.js transform pipeline + // recursively traverses the structure before validateQuery() is reached + let where = { username: 'test' }; + for (let i = 0; i < 50; i++) { + where = { $and: [where] }; + } + await expectAsync( + rest.find(config, auth.nobody(config), '_User', where) + ).toBeRejectedWith( + jasmine.objectContaining({ + message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/), + }) + ); + }); + + it('rejects deeply nested query via REST API without authentication', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + let where = { username: 'test' }; + for (let i = 0; i < 50; i++) { + where = { $or: [where] }; + } + await expectAsync( + request({ + method: 'GET', + url: `${Parse.serverURL}/classes/_User`, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + }, + qs: { where: JSON.stringify(where) }, + }) + ).toBeRejectedWith( + jasmine.objectContaining({ + data: jasmine.objectContaining({ + code: Parse.Error.INVALID_QUERY, + }), + }) + ); + }); + + it('rejects deeply nested $nor query before transform pipeline', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + const auth = require('../lib/Auth'); + const rest = require('../lib/rest'); + const config = Config.get('test'); + let where = { username: 'test' }; + for (let i = 0; i < 50; i++) { + where = { $nor: [where] }; + } + await expectAsync( + rest.find(config, auth.nobody(config), '_User', where) + ).toBeRejectedWith( + jasmine.objectContaining({ + message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/), + }) + ); + }); + + it('allows queries within the depth limit', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + const auth = require('../lib/Auth'); + const rest = require('../lib/rest'); + const config = Config.get('test'); + let where = { username: 'test' }; + for (let i = 0; i < 5; i++) { + where = { $or: [where] }; + } + const result = await rest.find(config, auth.nobody(config), '_User', where); + expect(result.results).toBeDefined(); + }); }); describe('(GHSA-fjxm-vhvc-gcmj) LiveQuery Operator Type Confusion', () => {
src/RestQuery.js+33 −0 modified@@ -281,6 +281,9 @@ function _UnsafeRestQuery( // TODO: consolidate the replaceX functions _UnsafeRestQuery.prototype.execute = function (executeOptions) { return Promise.resolve() + .then(() => { + return this.validateQueryDepth(); + }) .then(() => { return this.buildRestWhere(); }) @@ -352,6 +355,36 @@ _UnsafeRestQuery.prototype.each = function (callback) { ); }; +_UnsafeRestQuery.prototype.validateQueryDepth = function () { + if (this.auth.isMaster || this.auth.isMaintenance) { + return; + } + const rc = this.config.requestComplexity; + if (!rc || rc.queryDepth === -1) { + return; + } + const maxDepth = rc.queryDepth; + const checkDepth = (where, depth) => { + if (depth > maxDepth) { + throw new Parse.Error( + Parse.Error.INVALID_QUERY, + `Query condition nesting depth exceeds maximum allowed depth of ${maxDepth}` + ); + } + if (typeof where !== 'object' || where === null) { + return; + } + for (const op of ['$or', '$and', '$nor']) { + if (Array.isArray(where[op])) { + for (const subQuery of where[op]) { + checkDepth(subQuery, depth + 1); + } + } + } + }; + checkDepth(this.restWhere, 0); +}; + _UnsafeRestQuery.prototype.buildRestWhere = function () { return Promise.resolve() .then(() => {
2581b5426047fix: Query condition depth bypass via pre-validation transform pipeline ([GHSA-9fjp-q3c4-6w3j](https://github.com/parse-community/parse-server/security/advisories/GHSA-9fjp-q3c4-6w3j)) (#10258)
2 files changed · +117 −0
spec/vulnerabilities.spec.js+84 −0 modified@@ -2700,6 +2700,90 @@ describe('(GHSA-9xp9-j92r-p88v) Stack overflow process crash via deeply nested q ); }); + it('rejects deeply nested query before transform pipeline processes it', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + const auth = require('../lib/Auth'); + const rest = require('../lib/rest'); + const config = Config.get('test'); + // Depth 50 bypasses the fix because RestQuery.js transform pipeline + // recursively traverses the structure before validateQuery() is reached + let where = { username: 'test' }; + for (let i = 0; i < 50; i++) { + where = { $and: [where] }; + } + await expectAsync( + rest.find(config, auth.nobody(config), '_User', where) + ).toBeRejectedWith( + jasmine.objectContaining({ + message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/), + }) + ); + }); + + it('rejects deeply nested query via REST API without authentication', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + let where = { username: 'test' }; + for (let i = 0; i < 50; i++) { + where = { $or: [where] }; + } + await expectAsync( + request({ + method: 'GET', + url: `${Parse.serverURL}/classes/_User`, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + }, + qs: { where: JSON.stringify(where) }, + }) + ).toBeRejectedWith( + jasmine.objectContaining({ + data: jasmine.objectContaining({ + code: Parse.Error.INVALID_QUERY, + }), + }) + ); + }); + + it('rejects deeply nested $nor query before transform pipeline', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + const auth = require('../lib/Auth'); + const rest = require('../lib/rest'); + const config = Config.get('test'); + let where = { username: 'test' }; + for (let i = 0; i < 50; i++) { + where = { $nor: [where] }; + } + await expectAsync( + rest.find(config, auth.nobody(config), '_User', where) + ).toBeRejectedWith( + jasmine.objectContaining({ + message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/), + }) + ); + }); + + it('allows queries within the depth limit', async () => { + await reconfigureServer({ + requestComplexity: { queryDepth: 10 }, + }); + const auth = require('../lib/Auth'); + const rest = require('../lib/rest'); + const config = Config.get('test'); + let where = { username: 'test' }; + for (let i = 0; i < 5; i++) { + where = { $or: [where] }; + } + const result = await rest.find(config, auth.nobody(config), '_User', where); + expect(result.results).toBeDefined(); + }); + describe('(GHSA-wjqw-r9x4-j59v) Empty authData session issuance bypass', () => { const signupHeaders = { 'Content-Type': 'application/json',
src/RestQuery.js+33 −0 modified@@ -281,6 +281,9 @@ function _UnsafeRestQuery( // TODO: consolidate the replaceX functions _UnsafeRestQuery.prototype.execute = function (executeOptions) { return Promise.resolve() + .then(() => { + return this.validateQueryDepth(); + }) .then(() => { return this.buildRestWhere(); }) @@ -352,6 +355,36 @@ _UnsafeRestQuery.prototype.each = function (callback) { ); }; +_UnsafeRestQuery.prototype.validateQueryDepth = function () { + if (this.auth.isMaster || this.auth.isMaintenance) { + return; + } + const rc = this.config.requestComplexity; + if (!rc || rc.queryDepth === -1) { + return; + } + const maxDepth = rc.queryDepth; + const checkDepth = (where, depth) => { + if (depth > maxDepth) { + throw new Parse.Error( + Parse.Error.INVALID_QUERY, + `Query condition nesting depth exceeds maximum allowed depth of ${maxDepth}` + ); + } + if (typeof where !== 'object' || where === null) { + return; + } + for (const op of ['$or', '$and', '$nor']) { + if (Array.isArray(where[op])) { + for (const subQuery of where[op]) { + checkDepth(subQuery, depth + 1); + } + } + } + }; + checkDepth(this.restWhere, 0); +}; + _UnsafeRestQuery.prototype.buildRestWhere = function () { return Promise.resolve() .then(() => {
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/advisories/GHSA-9fjp-q3c4-6w3jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-33498ghsaADVISORY
- github.com/parse-community/parse-server/commit/2581b5426047ce9cbcd3d9c0e8379e9c30e23ab5ghsax_refsource_MISCWEB
- github.com/parse-community/parse-server/commit/85994eff9e7b34cac7e1a2f5791985022a1461d1ghsax_refsource_MISCWEB
- github.com/parse-community/parse-server/pull/10257ghsax_refsource_MISCWEB
- github.com/parse-community/parse-server/pull/10258ghsax_refsource_MISCWEB
- github.com/parse-community/parse-server/security/advisories/GHSA-9fjp-q3c4-6w3jghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.