VYPR
High severity8.8NVD Advisory· Published Mar 31, 2026· Updated Apr 2, 2026

CVE-2026-34373

CVE-2026-34373

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.66 and 9.7.0-alpha.10, the GraphQL API endpoint does not respect the allowOrigin server option and unconditionally allows cross-origin requests from any website. This bypasses origin restrictions that operators configure to control which websites can interact with the Parse Server API. The REST API correctly enforces the configured allowOrigin restriction. This issue has been patched in versions 8.6.66 and 9.7.0-alpha.10.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
parse-servernpm
>= 9.0.0, < 9.7.0-alpha.109.7.0-alpha.10
parse-servernpm
>= 3.5.0, < 8.6.668.6.66

Affected products

10
  • cpe:2.3:a:parseplatform:parse-server:9.7.0:alpha1:*:*:*:node.js:*:*+ 9 more
    • 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: >=3.5.0,<8.6.66

Patches

2
034764150789

fix: GraphQL API endpoint ignores CORS origin restriction ([GHSA-q3p6-g7c4-829c](https://github.com/parse-community/parse-server/security/advisories/GHSA-q3p6-g7c4-829c)) (#10335)

3 files changed · +124 7
  • spec/ParseGraphQLServer.spec.js+2 2 modified
    @@ -503,7 +503,7 @@ describe('ParseGraphQLServer', () => {
             }
           });
     
    -      it('should be cors enabled and scope the response within the source origin', async () => {
    +      it('should be cors enabled', async () => {
             let checked = false;
             const apolloClient = new ApolloClient({
               link: new ApolloLink((operation, forward) => {
    @@ -512,7 +512,7 @@ describe('ParseGraphQLServer', () => {
                   const {
                     response: { headers },
                   } = context;
    -              expect(headers.get('access-control-allow-origin')).toEqual('http://example.com');
    +              expect(headers.get('access-control-allow-origin')).toEqual('*');
                   checked = true;
                   return response;
                 });
    
  • spec/vulnerabilities.spec.js+119 0 modified
    @@ -4645,4 +4645,123 @@ describe('(GHSA-wp76-gg32-8258) /verifyPassword leaks raw authData via missing a
         expect(response.data.authData?.mfa?.recovery).toBeUndefined();
         expect(response.data.authData?.mfa).toEqual({ status: 'enabled' });
       });
    +
    +  describe('(GHSA-q3p6-g7c4-829c) GraphQL endpoint ignores allowOrigin server option', () => {
    +    let httpServer;
    +    const gqlPort = 13398;
    +
    +    const gqlHeaders = {
    +      'X-Parse-Application-Id': 'test',
    +      'X-Parse-Javascript-Key': 'test',
    +      'Content-Type': 'application/json',
    +    };
    +
    +    async function setupGraphQLServer(serverOptions = {}) {
    +      if (httpServer) {
    +        await new Promise(resolve => httpServer.close(resolve));
    +      }
    +      const server = await reconfigureServer(serverOptions);
    +      const expressApp = express();
    +      httpServer = http.createServer(expressApp);
    +      expressApp.use('/parse', server.app);
    +      const parseGraphQLServer = new ParseGraphQLServer(server, {
    +        graphQLPath: '/graphql',
    +      });
    +      parseGraphQLServer.applyGraphQL(expressApp);
    +      await new Promise(resolve => httpServer.listen({ port: gqlPort }, resolve));
    +      return parseGraphQLServer;
    +    }
    +
    +    afterEach(async () => {
    +      if (httpServer) {
    +        await new Promise(resolve => httpServer.close(resolve));
    +        httpServer = null;
    +      }
    +    });
    +
    +    it('should reflect allowed origin when allowOrigin is configured', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(response.status).toBe(200);
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +
    +    it('should not reflect unauthorized origin when allowOrigin is configured', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://unauthorized.example.net' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(response.headers.get('access-control-allow-origin')).not.toBe('https://unauthorized.example.net');
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +
    +    it('should support multiple allowed origins', async () => {
    +      await setupGraphQLServer({ allowOrigin: ['https://a.example.com', 'https://b.example.com'] });
    +      const responseA = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://a.example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(responseA.headers.get('access-control-allow-origin')).toBe('https://a.example.com');
    +
    +      const responseB = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://b.example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(responseB.headers.get('access-control-allow-origin')).toBe('https://b.example.com');
    +
    +      const responseUnauthorized = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://unauthorized.example.net' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(responseUnauthorized.headers.get('access-control-allow-origin')).not.toBe('https://unauthorized.example.net');
    +      expect(responseUnauthorized.headers.get('access-control-allow-origin')).toBe('https://a.example.com');
    +    });
    +
    +    it('should default to wildcard when allowOrigin is not configured', async () => {
    +      await setupGraphQLServer();
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(response.headers.get('access-control-allow-origin')).toBe('*');
    +    });
    +
    +    it('should handle OPTIONS preflight with configured allowOrigin', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'OPTIONS',
    +        headers: {
    +          Origin: 'https://example.com',
    +          'Access-Control-Request-Method': 'POST',
    +          'Access-Control-Request-Headers': 'X-Parse-Application-Id, Content-Type',
    +        },
    +      });
    +      expect(response.status).toBe(200);
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +
    +    it('should not reflect unauthorized origin in OPTIONS preflight', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'OPTIONS',
    +        headers: {
    +          Origin: 'https://unauthorized.example.net',
    +          'Access-Control-Request-Method': 'POST',
    +          'Access-Control-Request-Headers': 'X-Parse-Application-Id, Content-Type',
    +        },
    +      });
    +      expect(response.headers.get('access-control-allow-origin')).not.toBe('https://unauthorized.example.net');
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +  });
     });
    
  • src/GraphQL/ParseGraphQLServer.js+3 5 modified
    @@ -1,11 +1,10 @@
    -import corsMiddleware from 'cors';
     import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js';
     import { ApolloServer } from '@apollo/server';
     import { expressMiddleware } from '@apollo/server/express4';
     import { ApolloServerPluginCacheControlDisabled } from '@apollo/server/plugin/disabled';
     import express from 'express';
     import { GraphQLError } from 'graphql';
    -import { handleParseErrors, handleParseHeaders, handleParseSession } from '../middlewares';
    +import { allowCrossDomain, handleParseErrors, handleParseHeaders, handleParseSession } from '../middlewares';
     import requiredParameter from '../requiredParameter';
     import { createComplexityValidationPlugin } from './helpers/queryComplexity';
     import defaultLogger from '../logger';
    @@ -76,8 +75,7 @@ class ParseGraphQLServer {
         try {
           return {
             schema: await this.parseGraphQLSchema.load(),
    -        context: async ({ req, res }) => {
    -          res.set('access-control-allow-origin', req.get('origin') || '*');
    +        context: async ({ req }) => {
               return {
                 info: req.info,
                 config: req.config,
    @@ -162,7 +160,7 @@ class ParseGraphQLServer {
         if (!app || !app.use) {
           requiredParameter('You must provide an Express.js app instance!');
         }
    -    app.use(this.config.graphQLPath, corsMiddleware());
    +    app.use(this.config.graphQLPath, allowCrossDomain(this.parseServer.config.appId));
         app.use(this.config.graphQLPath, handleParseHeaders);
         app.use(this.config.graphQLPath, handleParseSession);
         this.applyRequestContextMiddleware(app, this.parseServer.config);
    
4dd0d3d8be1c

fix: GraphQL API endpoint ignores CORS origin restriction ([GHSA-q3p6-g7c4-829c](https://github.com/parse-community/parse-server/security/advisories/GHSA-q3p6-g7c4-829c)) (#10334)

3 files changed · +124 7
  • spec/ParseGraphQLServer.spec.js+2 2 modified
    @@ -503,7 +503,7 @@ describe('ParseGraphQLServer', () => {
             }
           });
     
    -      it('should be cors enabled and scope the response within the source origin', async () => {
    +      it('should be cors enabled', async () => {
             let checked = false;
             const apolloClient = new ApolloClient({
               link: new ApolloLink((operation, forward) => {
    @@ -512,7 +512,7 @@ describe('ParseGraphQLServer', () => {
                   const {
                     response: { headers },
                   } = context;
    -              expect(headers.get('access-control-allow-origin')).toEqual('http://example.com');
    +              expect(headers.get('access-control-allow-origin')).toEqual('*');
                   checked = true;
                   return response;
                 });
    
  • spec/vulnerabilities.spec.js+119 0 modified
    @@ -5103,4 +5103,123 @@ describe('(GHSA-p2w6-rmh7-w8q3) SQL Injection via aggregate and distinct field n
           expect(response.data.authData?.mfa).toEqual({ status: 'enabled' });
         });
       });
    +
    +  describe('(GHSA-q3p6-g7c4-829c) GraphQL endpoint ignores allowOrigin server option', () => {
    +    let httpServer;
    +    const gqlPort = 13398;
    +
    +    const gqlHeaders = {
    +      'X-Parse-Application-Id': 'test',
    +      'X-Parse-Javascript-Key': 'test',
    +      'Content-Type': 'application/json',
    +    };
    +
    +    async function setupGraphQLServer(serverOptions = {}) {
    +      if (httpServer) {
    +        await new Promise(resolve => httpServer.close(resolve));
    +      }
    +      const server = await reconfigureServer(serverOptions);
    +      const expressApp = express();
    +      httpServer = http.createServer(expressApp);
    +      expressApp.use('/parse', server.app);
    +      const parseGraphQLServer = new ParseGraphQLServer(server, {
    +        graphQLPath: '/graphql',
    +      });
    +      parseGraphQLServer.applyGraphQL(expressApp);
    +      await new Promise(resolve => httpServer.listen({ port: gqlPort }, resolve));
    +      return parseGraphQLServer;
    +    }
    +
    +    afterEach(async () => {
    +      if (httpServer) {
    +        await new Promise(resolve => httpServer.close(resolve));
    +        httpServer = null;
    +      }
    +    });
    +
    +    it('should reflect allowed origin when allowOrigin is configured', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(response.status).toBe(200);
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +
    +    it('should not reflect unauthorized origin when allowOrigin is configured', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://unauthorized.example.net' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(response.headers.get('access-control-allow-origin')).not.toBe('https://unauthorized.example.net');
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +
    +    it('should support multiple allowed origins', async () => {
    +      await setupGraphQLServer({ allowOrigin: ['https://a.example.com', 'https://b.example.com'] });
    +      const responseA = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://a.example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(responseA.headers.get('access-control-allow-origin')).toBe('https://a.example.com');
    +
    +      const responseB = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://b.example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(responseB.headers.get('access-control-allow-origin')).toBe('https://b.example.com');
    +
    +      const responseUnauthorized = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://unauthorized.example.net' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(responseUnauthorized.headers.get('access-control-allow-origin')).not.toBe('https://unauthorized.example.net');
    +      expect(responseUnauthorized.headers.get('access-control-allow-origin')).toBe('https://a.example.com');
    +    });
    +
    +    it('should default to wildcard when allowOrigin is not configured', async () => {
    +      await setupGraphQLServer();
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'POST',
    +        headers: { ...gqlHeaders, Origin: 'https://example.com' },
    +        body: JSON.stringify({ query: '{ health }' }),
    +      });
    +      expect(response.headers.get('access-control-allow-origin')).toBe('*');
    +    });
    +
    +    it('should handle OPTIONS preflight with configured allowOrigin', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'OPTIONS',
    +        headers: {
    +          Origin: 'https://example.com',
    +          'Access-Control-Request-Method': 'POST',
    +          'Access-Control-Request-Headers': 'X-Parse-Application-Id, Content-Type',
    +        },
    +      });
    +      expect(response.status).toBe(200);
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +
    +    it('should not reflect unauthorized origin in OPTIONS preflight', async () => {
    +      await setupGraphQLServer({ allowOrigin: 'https://example.com' });
    +      const response = await fetch(`http://localhost:${gqlPort}/graphql`, {
    +        method: 'OPTIONS',
    +        headers: {
    +          Origin: 'https://unauthorized.example.net',
    +          'Access-Control-Request-Method': 'POST',
    +          'Access-Control-Request-Headers': 'X-Parse-Application-Id, Content-Type',
    +        },
    +      });
    +      expect(response.headers.get('access-control-allow-origin')).not.toBe('https://unauthorized.example.net');
    +      expect(response.headers.get('access-control-allow-origin')).toBe('https://example.com');
    +    });
    +  });
     });
    
  • src/GraphQL/ParseGraphQLServer.js+3 5 modified
    @@ -1,11 +1,10 @@
    -import corsMiddleware from 'cors';
     import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js';
     import { ApolloServer } from '@apollo/server';
     import { expressMiddleware } from '@as-integrations/express5';
     import { ApolloServerPluginCacheControlDisabled } from '@apollo/server/plugin/disabled';
     import express from 'express';
     import { GraphQLError, parse } from 'graphql';
    -import { handleParseErrors, handleParseHeaders, handleParseSession } from '../middlewares';
    +import { allowCrossDomain, handleParseErrors, handleParseHeaders, handleParseSession } from '../middlewares';
     import requiredParameter from '../requiredParameter';
     import defaultLogger from '../logger';
     import { ParseGraphQLSchema } from './ParseGraphQLSchema';
    @@ -116,8 +115,7 @@ class ParseGraphQLServer {
         try {
           return {
             schema: await this.parseGraphQLSchema.load(),
    -        context: async ({ req, res }) => {
    -          res.set('access-control-allow-origin', req.get('origin') || '*');
    +        context: async ({ req }) => {
               return {
                 info: req.info,
                 config: req.config,
    @@ -204,7 +202,7 @@ class ParseGraphQLServer {
         if (!app || !app.use) {
           requiredParameter('You must provide an Express.js app instance!');
         }
    -    app.use(this.config.graphQLPath, corsMiddleware());
    +    app.use(this.config.graphQLPath, allowCrossDomain(this.parseServer.config.appId));
         app.use(this.config.graphQLPath, handleParseHeaders);
         app.use(this.config.graphQLPath, handleParseSession);
         this.applyRequestContextMiddleware(app, this.parseServer.config);
    

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.