Parse Server's GraphQL "Did you mean ...?" validation suggestions disclose schema to unauthenticated callers
Description
Impact
Parse Server's GraphQL endpoint discloses schema metadata to unauthenticated callers through Did you mean ...? suggestions embedded in GraphQL validation-error messages. An unauthenticated caller who knows only the public application id can iteratively send malformed queries to reconstruct class names, field names, argument names, mutation names, and input-object fields. This bypasses the IntrospectionControlPlugin enforced when graphQLPublicIntrospection: false (the default) and defeats the schema-hiding goal of prior advisories GHSA-48q3-prgv-gm4w and GHSA-q5q9-2rhp-33qw. Schema disclosure aids reconnaissance for downstream authorization probing but does not by itself leak object data or authentication material.
Patches
A new SchemaSuggestionsControlPlugin Apollo plugin strips the Did you mean ...? suffix from GraphQL validation-error messages during validationDidStart, which runs before any introspection gate. The plugin applies only when graphQLPublicIntrospection: false and the caller is not a master-key or maintenance-key holder, matching the trust model of the existing IntrospectionControlPlugin.
Workarounds
No code workaround is available short of disabling the GraphQL API (mountGraphQL: false). Operators who require disclosure-resistant validation errors should upgrade to a patched release.
Resources
- GitHub security advisory: https://github.com/parse-community/parse-server/security/advisories/GHSA-8cph-rgr4-g5vj
- Fix Parse Server 9: https://github.com/parse-community/parse-server/pull/10467
- Fix Parse Server 8: https://github.com/parse-community/parse-server/pull/10468
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Parse Server's GraphQL endpoint leaks schema metadata via "Did you mean...?" suggestions to unauthenticated callers, bypassing introspection controls.
Vulnerability
Parse Server versions 8.6.78 and earlier, and 9.9.1-alpha.2 and earlier, expose schema details through Did you mean ...? suggestions embedded in GraphQL validation-error messages [1][4]. An unauthenticated caller who knows the public application ID can send intentionally malformed queries to reconstruct class names, field names, argument names, mutation names, and input-object fields, bypassing the IntrospectionControlPlugin when graphQLPublicIntrospection: false (the default) [3].
Exploitation
An attacker with only the public application ID can iteratively craft invalid GraphQL queries, observe the Did you mean ...? suggestions in validation errors, and use those hints to infer the schema [3]. The attack requires no authentication, no user interaction, and no special network position—only network access to the GraphQL endpoint and knowledge of the application ID [4].
Impact
Schema disclosure aids reconnaissance for downstream attacks, such as probing authorization rules or crafting targeted queries, but does not directly leak object data, authentication material, or secrets [1][3]. The attacker learns the structure of classes, fields, arguments, mutations, and input objects, enabling more focused exploitation of other vulnerabilities [4].
Mitigation
Parse Server 8.6.78 (Parse Server 8) and 9.9.1-alpha.2 (Parse Server 9) include a new SchemaSuggestionsControlPlugin that strips Did you mean ...? suggestions from validation errors during validationDidStart when graphQLPublicIntrospection: false and the caller lacks master or maintenance keys [2][3]. No code workaround exists short of disabling the GraphQL API via mountGraphQL: false [4]. Operators who need disclosure-resistant validation errors should upgrade to the patched releases.
- fix: GraphQL "Did you mean" validation suggestions disclose schema to unauthenticated callers (GHSA-8cph-rgr4-g5vj) by mtrezza · Pull Request #10468 · parse-community/parse-server
- fix: GraphQL "Did you mean" validation suggestions disclose schema to unauthenticated callers (GHSA-8cph-rgr4-g5vj) by mtrezza · Pull Request #10467 · parse-community/parse-server
- CVE-2026-47248 - GitHub Advisory Database
- GraphQL "Did you mean" validation suggestions disclose schema to unauthenticated callers
AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: < 8.6.78
Patches
2a0ddb850e106fix: GraphQL "Did you mean" validation suggestions disclose schema to unauthenticated callers ([GHSA-8cph-rgr4-g5vj](https://github.com/parse-community/parse-server/security/advisories/GHSA-8cph-rgr4-g5vj)) (#10468)
2 files changed · +137 −1
spec/ParseGraphQLServer.spec.js+109 −0 modified@@ -715,6 +715,115 @@ describe('ParseGraphQLServer', () => { }) expect(introspection.data).toBeDefined(); }); + + it('should strip "Did you mean" field suggestions from validation errors without master or maintenance key', async () => { + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).not.toMatch(/Did you mean/); + expect(message).not.toContain('health'); + } + }); + + it('should strip "Did you mean" argument suggestions from validation errors without master or maintenance key', async () => { + try { + await apolloClient.query({ + query: gql` + query UnknownArg { + users(wher: {}) { + edges { + node { + id + } + } + } + } + `, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Unknown argument "wher"'); + expect(message).not.toMatch(/Did you mean/); + expect(message).not.toContain('"where"'); + } + }); + + it('should keep "Did you mean" suggestions with master key', async () => { + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).toMatch(/Did you mean/); + expect(message).toContain('health'); + } + }); + + it('should keep "Did you mean" suggestions with maintenance key', async () => { + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + context: { + headers: { + 'X-Parse-Maintenance-Key': 'test2', + }, + }, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).toMatch(/Did you mean/); + expect(message).toContain('health'); + } + }); + + it('should keep "Did you mean" suggestions when public introspection is enabled', async () => { + const parseServer = await reconfigureServer(); + await createGQLFromParseServer(parseServer, { graphQLPublicIntrospection: true }); + + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).toMatch(/Did you mean/); + expect(message).toContain('health'); + } + }); });
src/GraphQL/ParseGraphQLServer.js+28 −1 modified@@ -50,6 +50,33 @@ const IntrospectionControlPlugin = (publicIntrospection) => ({ }); +// graphql-js validation rules (FieldsOnCorrectTypeRule, KnownArgumentNamesRule, +// KnownTypeNamesRule, ...) embed "Did you mean ...?" hints sourced from the live +// schema in their error messages. Those messages are returned to the caller +// before didResolveOperation runs, so they sidestep IntrospectionControlPlugin +// and disclose schema identifiers the introspection guard is meant to hide. +// Strip the hint suffix for callers that are not allowed to introspect. +const SchemaSuggestionsControlPlugin = (publicIntrospection) => ({ + requestDidStart: async (requestContext) => ({ + validationDidStart: async () => { + if (publicIntrospection) { + return; + } + const isMasterOrMaintenance = + requestContext.contextValue.auth?.isMaster || + requestContext.contextValue.auth?.isMaintenance; + if (isMasterOrMaintenance) { + return; + } + return async (validationErrors) => { + validationErrors?.forEach(error => { + error.message = error.message.replace(/ ?Did you mean(.+?)\?$/, ''); + }); + }; + }, + }), +}); + class ParseGraphQLServer { parseGraphQLController: ParseGraphQLController; @@ -111,7 +138,7 @@ class ParseGraphQLServer { requestHeaders: ['X-Parse-Application-Id'], }, introspection: this.config.graphQLPublicIntrospection, - plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection), createComplexityValidationPlugin(() => this.parseServer.config.requestComplexity)], + plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection), SchemaSuggestionsControlPlugin(this.config.graphQLPublicIntrospection), createComplexityValidationPlugin(() => this.parseServer.config.requestComplexity)], schema, }); await apollo.start();
155123ade9bcfix: GraphQL "Did you mean" validation suggestions disclose schema to unauthenticated callers ([GHSA-8cph-rgr4-g5vj](https://github.com/parse-community/parse-server/security/advisories/GHSA-8cph-rgr4-g5vj)) (#10467)
2 files changed · +137 −1
spec/ParseGraphQLServer.spec.js+109 −0 modified@@ -1016,6 +1016,115 @@ describe('ParseGraphQLServer', () => { expect(introspection.data).toBeDefined(); expect(introspection.data.__type).toBeDefined(); }); + + it('should strip "Did you mean" field suggestions from validation errors without master or maintenance key', async () => { + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).not.toMatch(/Did you mean/); + expect(message).not.toContain('health'); + } + }); + + it('should strip "Did you mean" argument suggestions from validation errors without master or maintenance key', async () => { + try { + await apolloClient.query({ + query: gql` + query UnknownArg { + users(wher: {}) { + edges { + node { + id + } + } + } + } + `, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Unknown argument "wher"'); + expect(message).not.toMatch(/Did you mean/); + expect(message).not.toContain('"where"'); + } + }); + + it('should keep "Did you mean" suggestions with master key', async () => { + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + context: { + headers: { + 'X-Parse-Master-Key': 'test', + }, + }, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).toMatch(/Did you mean/); + expect(message).toContain('health'); + } + }); + + it('should keep "Did you mean" suggestions with maintenance key', async () => { + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + context: { + headers: { + 'X-Parse-Maintenance-Key': 'test2', + }, + }, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).toMatch(/Did you mean/); + expect(message).toContain('health'); + } + }); + + it('should keep "Did you mean" suggestions when public introspection is enabled', async () => { + const parseServer = await reconfigureServer(); + await createGQLFromParseServer(parseServer, { graphQLPublicIntrospection: true }); + + try { + await apolloClient.query({ + query: gql` + query Typo { + healt + } + `, + }); + fail('should have thrown a validation error'); + } catch (e) { + const message = e.networkError.result.errors[0].message; + expect(message).toContain('Cannot query field "healt"'); + expect(message).toMatch(/Did you mean/); + expect(message).toContain('health'); + } + }); });
src/GraphQL/ParseGraphQLServer.js+28 −1 modified@@ -90,6 +90,33 @@ const IntrospectionControlPlugin = (publicIntrospection) => ({ }); +// graphql-js validation rules (FieldsOnCorrectTypeRule, KnownArgumentNamesRule, +// KnownTypeNamesRule, ...) embed "Did you mean ...?" hints sourced from the live +// schema in their error messages. Those messages are returned to the caller +// before didResolveOperation runs, so they sidestep IntrospectionControlPlugin +// and disclose schema identifiers the introspection guard is meant to hide. +// Strip the hint suffix for callers that are not allowed to introspect. +const SchemaSuggestionsControlPlugin = (publicIntrospection) => ({ + requestDidStart: async (requestContext) => ({ + validationDidStart: async () => { + if (publicIntrospection) { + return; + } + const isMasterOrMaintenance = + requestContext.contextValue.auth?.isMaster || + requestContext.contextValue.auth?.isMaintenance; + if (isMasterOrMaintenance) { + return; + } + return async (validationErrors) => { + validationErrors?.forEach(error => { + error.message = error.message.replace(/ ?Did you mean(.+?)\?$/, ''); + }); + }; + }, + }), +}); + class ParseGraphQLServer { parseGraphQLController: ParseGraphQLController; @@ -153,7 +180,7 @@ class ParseGraphQLServer { // We need always true introspection because apollo server have changing behavior based on the NODE_ENV variable // we delegate the introspection control to the IntrospectionControlPlugin introspection: true, - plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection), createComplexityValidationPlugin(() => this.parseServer.config.requestComplexity)], + plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection), SchemaSuggestionsControlPlugin(this.config.graphQLPublicIntrospection), createComplexityValidationPlugin(() => this.parseServer.config.requestComplexity)], schema, }); await apollo.start();
Vulnerability mechanics
Root cause
"GraphQL validation error messages embed schema-derived "Did you mean" suggestions that are returned before the introspection gate runs, leaking schema metadata to unauthenticated callers."
Attack vector
An unauthenticated attacker who knows only the public Parse application ID sends malformed GraphQL queries (e.g. `{ healt }` or `users(wher: {})`) to the GraphQL endpoint. The server responds with validation errors that include "Did you mean …?" suggestions containing actual class names, field names, argument names, mutation names, and input-object fields [ref_id=1]. By iterating over many typos, the attacker reconstructs the schema, bypassing the `IntrospectionControlPlugin` that is enforced when `graphQLPublicIntrospection: false` [CWE-200].
Affected code
The vulnerability resides in `src/GraphQL/ParseGraphQLServer.js` where the `SchemaSuggestionsControlPlugin` was missing. GraphQL validation rules such as `FieldsOnCorrectTypeRule` and `KnownArgumentNamesRule` embed "Did you mean …?" hints sourced from the live schema in error messages that are returned before `IntrospectionControlPlugin` can gate them [patch_id=3105895][patch_id=3105896].
What the fix does
The patch introduces a new `SchemaSuggestionsControlPlugin` Apollo plugin that hooks into `validationDidStart`. When `graphQLPublicIntrospection` is `false` and the request does not carry a master or maintenance key, the plugin strips the "Did you mean …?" suffix from every validation error message using a regex replacement [patch_id=3105895][patch_id=3105896]. This closes the information leak at the point where the error messages are generated, before they reach the caller.
Preconditions
- configThe Parse Server must have the GraphQL API enabled (mountGraphQL not false).
- configgraphQLPublicIntrospection must be false (the default setting).
- inputThe attacker must know the public Parse application ID.
- authNo authentication is required; the attacker can be unauthenticated.
- networkThe attacker sends malformed GraphQL queries over the network.
Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.