fix: Data schema exposed via GraphQL API public introspection (GHSA-48q3-prgv-gm4w) (#9819)

This commit is contained in:
Manuel
2025-07-10 04:25:09 +02:00
committed by GitHub
parent 2c29756038
commit c58b2eb6eb
8 changed files with 193 additions and 22 deletions

View File

@@ -4,7 +4,7 @@ import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginCacheControlDisabled } from '@apollo/server/plugin/disabled';
import express from 'express';
import { execute, subscribe } from 'graphql';
import { execute, subscribe, GraphQLError } from 'graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { handleParseErrors, handleParseHeaders, handleParseSession } from '../middlewares';
import requiredParameter from '../requiredParameter';
@@ -12,6 +12,45 @@ import defaultLogger from '../logger';
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
import ParseGraphQLController, { ParseGraphQLConfig } from '../Controllers/ParseGraphQLController';
const IntrospectionControlPlugin = (publicIntrospection) => ({
requestDidStart: (requestContext) => ({
didResolveOperation: async () => {
// If public introspection is enabled, we allow all introspection queries
if (publicIntrospection) {
return;
}
const isMasterOrMaintenance = requestContext.contextValue.auth?.isMaster || requestContext.contextValue.auth?.isMaintenance
if (isMasterOrMaintenance) {
return;
}
// Now we check if the query is an introspection query
// this check strategy should work in 99.99% cases
// we can have an issue if a user name a field or class __schemaSomething
// we want to avoid a full AST check
const isIntrospectionQuery =
requestContext.request.query?.includes('__schema')
if (isIntrospectionQuery) {
throw new GraphQLError('Introspection is not allowed', {
extensions: {
http: {
status: 403,
},
}
});
}
},
})
});
class ParseGraphQLServer {
parseGraphQLController: ParseGraphQLController;
@@ -65,8 +104,8 @@ class ParseGraphQLServer {
// needed since we use graphql upload
requestHeaders: ['X-Parse-Application-Id'],
},
introspection: true,
plugins: [ApolloServerPluginCacheControlDisabled()],
introspection: this.config.graphQLPublicIntrospection,
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection)],
schema,
});
await apollo.start();
@@ -118,7 +157,7 @@ class ParseGraphQLServer {
app.get(
this.config.playgroundPath ||
requiredParameter('You must provide a config.playgroundPath to applyPlayground!'),
requiredParameter('You must provide a config.playgroundPath to applyPlayground!'),
(_req, res) => {
res.setHeader('Content-Type', 'text/html');
res.write(