Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 10, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 141 additions & 34 deletions spec/ParseGraphQLServer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

beforeEach(async () => {
parseServer = await global.reconfigureServer({
maintenanceKey: 'test2',
maxUploadSize: '1kb',
});
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
Expand Down Expand Up @@ -88,8 +89,8 @@

it('should initialize parseGraphQLSchema with a log controller', async () => {
const loggerAdapter = {
log: () => {},
error: () => {},
log: () => { },
error: () => { },
};
const parseServer = await global.reconfigureServer({
loggerAdapter,
Expand Down Expand Up @@ -124,10 +125,10 @@
info: new Object(),
config: new Object(),
auth: new Object(),
get: () => {},
get: () => { },
};
const res = {
set: () => {},
set: () => { },
};

it_id('0696675e-060f-414f-bc77-9d57f31807f5')(it)('should return schema and context with req\'s info, config and auth', async () => {
Expand Down Expand Up @@ -431,7 +432,7 @@
objects.push(object1, object2, object3, object4);
}

async function createGQLFromParseServer(_parseServer) {
async function createGQLFromParseServer(_parseServer, parseGraphQLServerOptions) {
if (parseLiveQueryServer) {
await parseLiveQueryServer.server.close();
}
Expand All @@ -448,6 +449,7 @@
graphQLPath: '/graphql',
playgroundPath: '/playground',
subscriptionsPath: '/subscriptions',
...parseGraphQLServerOptions,
});
parseGraphQLServer.applyGraphQL(expressApp);
parseGraphQLServer.applyPlayground(expressApp);
Expand Down Expand Up @@ -488,8 +490,8 @@
},
},
});
spyOn(console, 'warn').and.callFake(() => {});
spyOn(console, 'error').and.callFake(() => {});
spyOn(console, 'warn').and.callFake(() => { });
spyOn(console, 'error').and.callFake(() => { });
});

afterEach(async () => {
Expand Down Expand Up @@ -605,6 +607,96 @@
]);
};

describe('Introspection', () => {
it('should have public introspection disabled by default without master key', async () => {

try {
await apolloClient.query({
query: gql`
query Introspection {
__schema {
types {
name
}
}
}
`,
})

fail('should have thrown an error');

} catch (e) {
expect(e.message).toEqual('Response not successful: Received status code 403');
expect(e.networkError.result.errors[0].message).toEqual('Introspection is not allowed');
}
});

it('should always work with master key', async () => {
const introspection =
await apolloClient.query({
query: gql`
query Introspection {
__schema {
types {
name
}
}
}
`,
context: {
headers: {
'X-Parse-Master-Key': 'test',
},
}
},)
expect(introspection.data).toBeDefined();
expect(introspection.errors).not.toBeDefined();
});

it('should always work with maintenance key', async () => {
const introspection =
await apolloClient.query({
query: gql`
query Introspection {
__schema {
types {
name
}
}
}
`,
context: {
headers: {
'X-Parse-Maintenance-Key': 'test2',
},
}
},)
expect(introspection.data).toBeDefined();
expect(introspection.errors).not.toBeDefined();
});

it('should have public introspection enabled if enabled', async () => {

const parseServer = await reconfigureServer();
await createGQLFromParseServer(parseServer, { graphQLPublicIntrospection: true });

const introspection =
await apolloClient.query({
query: gql`
query Introspection {
__schema {
types {
name
}
}
}
`,
})
expect(introspection.data).toBeDefined();
});
});


describe('Default Types', () => {
it('should have Object scalar type', async () => {
const objectType = (
Expand Down Expand Up @@ -749,6 +841,11 @@
}
}
`,
context: {
headers: {
'X-Parse-Master-Key': 'test',
},
}
})
).data['__schema'].types.map(type => type.name);

Expand Down Expand Up @@ -780,6 +877,11 @@
}
}
`,
context: {
headers: {
'X-Parse-Master-Key': 'test',
},
}
})
).data['__schema'].types.map(type => type.name);

Expand Down Expand Up @@ -864,7 +966,7 @@
});

it('should have clientMutationId in call function input', async () => {
Parse.Cloud.define('hello', () => {});
Parse.Cloud.define('hello', () => { });

const callFunctionInputFields = (
await apolloClient.query({
Expand All @@ -886,7 +988,7 @@
});

it('should have clientMutationId in call function payload', async () => {
Parse.Cloud.define('hello', () => {});
Parse.Cloud.define('hello', () => { });

const callFunctionPayloadFields = (
await apolloClient.query({
Expand Down Expand Up @@ -1312,6 +1414,11 @@
}
}
`,
context: {
headers: {
'X-Parse-Master-Key': 'test',
},
}
})
).data['__schema'].types.map(type => type.name);

Expand Down Expand Up @@ -6553,7 +6660,7 @@
);
expect(
(await deleteObject(object4.className, object4.id)).data.delete[
object4.className.charAt(0).toLowerCase() + object4.className.slice(1)
object4.className.charAt(0).toLowerCase() + object4.className.slice(1)

Check failure on line 6663 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 16 spaces but found 14
]
).toEqual({ objectId: object4.id, __typename: 'PublicClass' });
await expectAsync(object4.fetch({ useMasterKey: true })).toBeRejectedWith(
Expand Down Expand Up @@ -7447,9 +7554,9 @@
it('should send reset password', async () => {
const clientMutationId = uuidv4();
const emailAdapter = {
sendVerificationEmail: () => {},
sendVerificationEmail: () => { },
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
sendMail: () => { },
};
parseServer = await global.reconfigureServer({
appName: 'test',
Expand Down Expand Up @@ -7488,11 +7595,11 @@
const clientMutationId = uuidv4();
let resetPasswordToken;
const emailAdapter = {
sendVerificationEmail: () => {},
sendVerificationEmail: () => { },
sendPasswordResetEmail: ({ link }) => {
resetPasswordToken = link.split('token=')[1].split('&')[0];
},
sendMail: () => {},
sendMail: () => { },
};
parseServer = await global.reconfigureServer({
appName: 'test',
Expand Down Expand Up @@ -7558,9 +7665,9 @@
it('should send verification email again', async () => {
const clientMutationId = uuidv4();
const emailAdapter = {
sendVerificationEmail: () => {},
sendVerificationEmail: () => { },
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
sendMail: () => { },
};
parseServer = await global.reconfigureServer({
appName: 'test',
Expand Down Expand Up @@ -11157,25 +11264,25 @@
},
});
const SomeClassType = new GraphQLObjectType({
name: 'SomeClass',
fields: {
nameUpperCase: {
type: new GraphQLNonNull(GraphQLString),
resolve: p => p.name.toUpperCase(),
},
type: { type: TypeEnum },
language: {
type: new GraphQLEnumType({
name: 'LanguageEnum',
values: {
fr: { value: 'fr' },
en: { value: 'en' },
},
}),
resolve: () => 'fr',
},
name: 'SomeClass',

Check failure on line 11267 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 12 spaces but found 10
fields: {

Check failure on line 11268 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 12 spaces but found 10
nameUpperCase: {

Check failure on line 11269 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 14 spaces but found 12
type: new GraphQLNonNull(GraphQLString),

Check failure on line 11270 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 16 spaces but found 14
resolve: p => p.name.toUpperCase(),

Check failure on line 11271 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 16 spaces but found 14
},

Check failure on line 11272 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 14 spaces but found 12
}),
type: { type: TypeEnum },

Check failure on line 11273 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 14 spaces but found 12
language: {

Check failure on line 11274 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 14 spaces but found 12
type: new GraphQLEnumType({

Check failure on line 11275 in spec/ParseGraphQLServer.spec.js

View workflow job for this annotation

GitHub Actions / Lint

Expected indentation of 16 spaces but found 14
name: 'LanguageEnum',
values: {
fr: { value: 'fr' },
en: { value: 'en' },
},
}),
resolve: () => 'fr',
},
},
}),
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
graphQLPath: '/graphql',
graphQLCustomTypeDefs: new GraphQLSchema({
Expand Down
4 changes: 4 additions & 0 deletions spec/SecurityCheckGroups.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('Security Check Groups', () => {
config.security.enableCheckLog = false;
config.allowClientClassCreation = false;
config.enableInsecureAuthAdapters = false;
config.graphQLPublicIntrospection = false;
await reconfigureServer(config);

const group = new CheckGroupServerConfig();
Expand All @@ -41,12 +42,14 @@ describe('Security Check Groups', () => {
expect(group.checks()[1].checkState()).toBe(CheckState.success);
expect(group.checks()[2].checkState()).toBe(CheckState.success);
expect(group.checks()[4].checkState()).toBe(CheckState.success);
expect(group.checks()[5].checkState()).toBe(CheckState.success);
});

it('checks fail correctly', async () => {
config.masterKey = 'insecure';
config.security.enableCheckLog = true;
config.allowClientClassCreation = true;
config.graphQLPublicIntrospection = true;
await reconfigureServer(config);

const group = new CheckGroupServerConfig();
Expand All @@ -55,6 +58,7 @@ describe('Security Check Groups', () => {
expect(group.checks()[1].checkState()).toBe(CheckState.fail);
expect(group.checks()[2].checkState()).toBe(CheckState.fail);
expect(group.checks()[4].checkState()).toBe(CheckState.fail);
expect(group.checks()[5].checkState()).toBe(CheckState.fail);
});
});

Expand Down
Loading
Loading