fix: GraphQL __type introspection bypass via inline fragments when public introspection is disabled ([GHSA-q5q9-2rhp-33qw](https://github.com/parse-community/parse-server/security/advisories/GHSA-q5q9-2rhp-33qw)) (#10111)

This commit is contained in:
Manuel
2026-03-06 02:25:34 +00:00
committed by GitHub
parent c6b747036b
commit 61261a5aa1
2 changed files with 64 additions and 13 deletions
+46
View File
@@ -920,6 +920,52 @@ describe('ParseGraphQLServer', () => {
}
});
it('should block __type introspection inside inline fragment without master key', async () => {
try {
await apolloClient.query({
query: gql`
query InlineFragmentBypass {
... on Query {
__type(name: "User") {
name
kind
}
}
}
`,
});
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 block __type introspection inside nested inline fragments without master key', async () => {
try {
await apolloClient.query({
query: gql`
query NestedInlineFragmentBypass {
... on Query {
... {
__type(name: "User") {
name
kind
}
}
}
}
`,
});
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 allow __type introspection with master key', async () => {
const introspection = await apolloClient.query({
query: gql`
+18 -13
View File
@@ -16,25 +16,30 @@ import ParseGraphQLController, { ParseGraphQLConfig } from '../Controllers/Parse
const hasTypeIntrospection = (query) => {
try {
const ast = parse(query);
// Check only root-level fields in the query
// Note: selection.name.value is the actual field name, so this correctly handles
// aliases like "myAlias: __type(...)" where name.value === "__type"
for (const definition of ast.definitions) {
if ((definition.kind === 'OperationDefinition' || definition.kind === 'FragmentDefinition') && definition.selectionSet) {
for (const selection of definition.selectionSet.selections) {
if (selection.kind === 'Field' && selection.name.value === '__type') {
// GraphQL's introspection __type field requires a 'name' argument
// This distinguishes it from potential user-defined __type fields
if (selection.arguments && selection.arguments.length > 0) {
return true;
}
const checkSelections = (selections) => {
for (const selection of selections) {
if (selection.kind === 'Field' && selection.name.value === '__type') {
if (selection.arguments && selection.arguments.length > 0) {
return true;
}
}
if (selection.selectionSet) {
if (checkSelections(selection.selectionSet.selections)) {
return true;
}
}
}
return false;
};
for (const definition of ast.definitions) {
if (definition.selectionSet) {
if (checkSelections(definition.selectionSet.selections)) {
return true;
}
}
}
return false;
} catch {
// If parsing fails, we assume it's not a valid query and let Apollo handle it
return false;
}
};