VYPR
High severityNVD Advisory· Published Mar 24, 2026· Updated Mar 26, 2026

Parse Server: Query condition depth bypass via pre-validation transform pipeline

CVE-2026-33498

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.

PackageAffected versionsPatched versions
parse-servernpm
>= 9.0.0, < 9.6.0-alpha.449.6.0-alpha.44
parse-servernpm
< 8.6.558.6.55

Affected products

1

Patches

2
85994eff9e7b

fix: 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(() => {
    
2581b5426047

fix: 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

News mentions

0

No linked articles in our index yet.