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

Parse Server: Denial of service via unindexed database query for unconfigured auth providers

CVE-2026-33538

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.58 and 9.6.0-alpha.52, an unauthenticated attacker can cause denial of service by sending authentication requests with arbitrary, unconfigured provider names. The server executes a database query for each unconfigured provider before rejecting the request, and since no database index exists for unconfigured providers, each request triggers a full collection scan on the user database. This can be parallelized to saturate database resources. This issue has been patched in versions 8.6.58 and 9.6.0-alpha.52.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
>= 9.0.0, < 9.6.0-alpha.529.6.0-alpha.52
parse-servernpm
< 8.6.588.6.58

Affected products

1

Patches

2
40eb442e0267

fix: Denial of service via unindexed database query for unconfigured auth providers ([GHSA-g4cf-xj29-wqqr](https://github.com/parse-community/parse-server/security/advisories/GHSA-g4cf-xj29-wqqr)) (#10271)

2 files changed · +71 1
  • spec/vulnerabilities.spec.js+64 0 modified
    @@ -3716,3 +3716,67 @@ describe('(GHSA-6qh5-m6g3-xhq6) LiveQuery query depth DoS via deeply nested subs
         expect(subscription).toBeDefined();
       });
     });
    +
    +describe('(GHSA-g4cf-xj29-wqqr) DoS via unindexed database query for unconfigured auth providers', () => {
    +  it('should not query database for unconfigured auth provider on signup', async () => {
    +    const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
    +    const spy = spyOn(databaseAdapter, 'find').and.callThrough();
    +    await expectAsync(
    +      new Parse.User().save({ authData: { nonExistentProvider: { id: 'test123' } } })
    +    ).toBeRejectedWith(
    +      new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.')
    +    );
    +    const authDataQueries = spy.calls.all().filter(call => {
    +      const query = call.args[2];
    +      return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
    +    });
    +    expect(authDataQueries.length).toBe(0);
    +  });
    +
    +  it('should not query database for unconfigured auth provider on challenge', async () => {
    +    const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
    +    const spy = spyOn(databaseAdapter, 'find').and.callThrough();
    +    await expectAsync(
    +      request({
    +        method: 'POST',
    +        url: Parse.serverURL + '/challenge',
    +        headers: {
    +          'X-Parse-Application-Id': Parse.applicationId,
    +          'X-Parse-REST-API-Key': 'rest',
    +          'Content-Type': 'application/json',
    +        },
    +        body: JSON.stringify({
    +          authData: { nonExistentProvider: { id: 'test123' } },
    +          challengeData: { nonExistentProvider: { token: 'abc' } },
    +        }),
    +      })
    +    ).toBeRejected();
    +    const authDataQueries = spy.calls.all().filter(call => {
    +      const query = call.args[2];
    +      return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
    +    });
    +    expect(authDataQueries.length).toBe(0);
    +  });
    +
    +  it('should still query database for configured auth provider', async () => {
    +    await reconfigureServer({
    +      auth: {
    +        myConfiguredProvider: {
    +          module: {
    +            validateAppId: () => Promise.resolve(),
    +            validateAuthData: () => Promise.resolve(),
    +          },
    +        },
    +      },
    +    });
    +    const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
    +    const spy = spyOn(databaseAdapter, 'find').and.callThrough();
    +    const user = new Parse.User();
    +    await user.save({ authData: { myConfiguredProvider: { id: 'validId', token: 'validToken' } } });
    +    const authDataQueries = spy.calls.all().filter(call => {
    +      const query = call.args[2];
    +      return query?.$or?.some(q => q['authData.myConfiguredProvider.id']);
    +    });
    +    expect(authDataQueries.length).toBeGreaterThan(0);
    +  });
    +});
    
  • src/Auth.js+7 1 modified
    @@ -424,7 +424,13 @@ const findUsersWithAuthData = async (config, authData, beforeFind) => {
         providers.map(async provider => {
           const providerAuthData = authData[provider];
     
    -      const adapter = config.authDataManager.getValidatorForProvider(provider)?.adapter;
    +      const validatorConfig = config.authDataManager.getValidatorForProvider(provider);
    +      // Skip database query for unconfigured providers to avoid unindexed collection scans;
    +      // the provider will be rejected later in handleAuthDataValidation with UNSUPPORTED_SERVICE
    +      if (!validatorConfig?.validator) {
    +        return null;
    +      }
    +      const adapter = validatorConfig.adapter;
           if (beforeFind && typeof adapter?.beforeFind === 'function') {
             await adapter.beforeFind(providerAuthData);
           }
    
fbac847499e5

fix: Denial of service via unindexed database query for unconfigured auth providers ([GHSA-g4cf-xj29-wqqr](https://github.com/parse-community/parse-server/security/advisories/GHSA-g4cf-xj29-wqqr)) (#10270)

2 files changed · +71 1
  • spec/vulnerabilities.spec.js+64 0 modified
    @@ -4142,3 +4142,67 @@ describe('(GHSA-6qh5-m6g3-xhq6) LiveQuery query depth DoS via deeply nested subs
         expect(subscription).toBeDefined();
       });
     });
    +
    +describe('(GHSA-g4cf-xj29-wqqr) DoS via unindexed database query for unconfigured auth providers', () => {
    +  it('should not query database for unconfigured auth provider on signup', async () => {
    +    const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
    +    const spy = spyOn(databaseAdapter, 'find').and.callThrough();
    +    await expectAsync(
    +      new Parse.User().save({ authData: { nonExistentProvider: { id: 'test123' } } })
    +    ).toBeRejectedWith(
    +      new Parse.Error(Parse.Error.UNSUPPORTED_SERVICE, 'This authentication method is unsupported.')
    +    );
    +    const authDataQueries = spy.calls.all().filter(call => {
    +      const query = call.args[2];
    +      return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
    +    });
    +    expect(authDataQueries.length).toBe(0);
    +  });
    +
    +  it('should not query database for unconfigured auth provider on challenge', async () => {
    +    const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
    +    const spy = spyOn(databaseAdapter, 'find').and.callThrough();
    +    await expectAsync(
    +      request({
    +        method: 'POST',
    +        url: Parse.serverURL + '/challenge',
    +        headers: {
    +          'X-Parse-Application-Id': Parse.applicationId,
    +          'X-Parse-REST-API-Key': 'rest',
    +          'Content-Type': 'application/json',
    +        },
    +        body: JSON.stringify({
    +          authData: { nonExistentProvider: { id: 'test123' } },
    +          challengeData: { nonExistentProvider: { token: 'abc' } },
    +        }),
    +      })
    +    ).toBeRejected();
    +    const authDataQueries = spy.calls.all().filter(call => {
    +      const query = call.args[2];
    +      return query?.$or?.some(q => q['authData.nonExistentProvider.id']);
    +    });
    +    expect(authDataQueries.length).toBe(0);
    +  });
    +
    +  it('should still query database for configured auth provider', async () => {
    +    await reconfigureServer({
    +      auth: {
    +        myConfiguredProvider: {
    +          module: {
    +            validateAppId: () => Promise.resolve(),
    +            validateAuthData: () => Promise.resolve(),
    +          },
    +        },
    +      },
    +    });
    +    const databaseAdapter = Config.get(Parse.applicationId).database.adapter;
    +    const spy = spyOn(databaseAdapter, 'find').and.callThrough();
    +    const user = new Parse.User();
    +    await user.save({ authData: { myConfiguredProvider: { id: 'validId', token: 'validToken' } } });
    +    const authDataQueries = spy.calls.all().filter(call => {
    +      const query = call.args[2];
    +      return query?.$or?.some(q => q['authData.myConfiguredProvider.id']);
    +    });
    +    expect(authDataQueries.length).toBeGreaterThan(0);
    +  });
    +});
    
  • src/Auth.js+7 1 modified
    @@ -448,7 +448,13 @@ const findUsersWithAuthData = async (config, authData, beforeFind, currentUserAu
           const isUnchanged = storedProviderData && incomingKeys.length > 0 &&
             !incomingKeys.some(key => !isDeepStrictEqual(providerAuthData[key], storedProviderData[key]));
     
    -      const adapter = config.authDataManager.getValidatorForProvider(provider)?.adapter;
    +      const validatorConfig = config.authDataManager.getValidatorForProvider(provider);
    +      // Skip database query for unconfigured providers to avoid unindexed collection scans;
    +      // the provider will be rejected later in handleAuthDataValidation with UNSUPPORTED_SERVICE
    +      if (!validatorConfig?.validator) {
    +        return null;
    +      }
    +      const adapter = validatorConfig.adapter;
           if (beforeFind && typeof adapter?.beforeFind === 'function' && !isUnchanged) {
             await adapter.beforeFind(providerAuthData);
           }
    

Vulnerability mechanics

Synthesis attempt was rejected by the grounding validator. Re-run pending.

References

7

News mentions

0

No linked articles in our index yet.