diff --git a/DEPRECATIONS.md b/DEPRECATIONS.md index 7e6cbfdb49..f1b90fbd2d 100644 --- a/DEPRECATIONS.md +++ b/DEPRECATIONS.md @@ -21,6 +21,7 @@ The following is a list of deprecations, according to the [Deprecation Policy](h | DEPPS15 | Config option `readOnlyMasterKeyIps` defaults to `['127.0.0.1', '::1']` | [#10115](https://github.com/parse-community/parse-server/pull/10115) | 9.5.0 (2026) | 10.0.0 (2027) | deprecated | - | | DEPPS16 | Remove config option `mountPlayground` | [#10110](https://github.com/parse-community/parse-server/issues/10110) | 9.5.0 (2026) | 10.0.0 (2027) | deprecated | - | | DEPPS17 | Remove config option `playgroundPath` | [#10110](https://github.com/parse-community/parse-server/issues/10110) | 9.5.0 (2026) | 10.0.0 (2027) | deprecated | - | +| DEPPS18 | Config option `requestComplexity` defaults to enabled with limits | [#10204](https://github.com/parse-community/parse-server/pull/10204) | 9.6.0 (2026) | 10.0.0 (2027) | deprecated | - | [i_deprecation]: ## "The version and date of the deprecation." [i_change]: ## "The version and date of the planned change." diff --git a/spec/RequestComplexity.spec.js b/spec/RequestComplexity.spec.js index 6ee159f548..f352ddc1c2 100644 --- a/spec/RequestComplexity.spec.js +++ b/spec/RequestComplexity.spec.js @@ -116,9 +116,31 @@ describe('request complexity', () => { expect(config.requestComplexity.graphQLFields).toBe(200); }); - it('should apply full defaults when not configured', async () => { + it('should have requestComplexity undefined when not configured', async () => { await reconfigureServer({}); const config = Config.get('test'); + expect(config.requestComplexity).toBeUndefined(); + }); + + it('should apply no limits when requestComplexity is undefined', async () => { + await reconfigureServer({}); + const config = Config.get('test'); + expect(config.requestComplexity).toBeUndefined(); + + const where = buildNestedInQuery(15); + await expectAsync( + rest.find(config, auth.nobody(config), '_User', where) + ).toBeResolved(); + + const includes = Array.from({ length: 100 }, (_, i) => `field${i}`).join(','); + await expectAsync( + rest.find(config, auth.nobody(config), '_User', {}, { include: includes }) + ).toBeResolved(); + }); + + it('should apply full defaults when empty object is passed', async () => { + await reconfigureServer({ requestComplexity: {} }); + const config = Config.get('test'); expect(config.requestComplexity).toEqual({ includeDepth: 5, includeCount: 50, @@ -127,6 +149,18 @@ describe('request complexity', () => { graphQLFields: 200, }); }); + + it('should log deprecation warning when requestComplexity is not set', async () => { + const Deprecator = require('../lib/Deprecator/Deprecator'); + const logSpy = spyOn(Deprecator, '_logOption').and.callThrough(); + await reconfigureServer({}); + expect(logSpy).toHaveBeenCalledWith( + jasmine.objectContaining({ + optionKey: 'requestComplexity', + changeNewDefault: jasmine.stringMatching(/includeDepth.*10/), + }) + ); + }); }); describe('subquery depth', () => { diff --git a/src/Deprecator/Deprecations.js b/src/Deprecator/Deprecations.js index 60e37e6efb..c8e71cdeb3 100644 --- a/src/Deprecator/Deprecations.js +++ b/src/Deprecator/Deprecations.js @@ -41,4 +41,9 @@ module.exports = [ changeNewKey: '', solution: "Use Parse Dashboard as GraphQL IDE or configure a third-party GraphQL client such as Apollo Sandbox, GraphiQL, or Insomnia with custom request headers.", }, + { + optionKey: 'requestComplexity', + changeNewDefault: '{"includeDepth":10,"includeCount":100,"subqueryDepth":10,"graphQLDepth":20,"graphQLFields":200}', + solution: "Set 'requestComplexity' to the limits you want to enforce, or to the above object to opt-in to the future default behavior.", + }, ]; diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index aa9ff33e94..b7fd8ce017 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -503,7 +503,6 @@ module.exports.ParseServerOptions = { help: 'Options to limit the complexity of requests to prevent denial-of-service attacks. Limits are enforced for all requests except those using the master or maintenance key. Each property can be set to `-1` to disable that specific limit.', action: parsers.objectParser, type: 'RequestComplexityOptions', - default: {}, }, requestContextMiddleware: { env: 'PARSE_SERVER_REQUEST_CONTEXT_MIDDLEWARE', diff --git a/src/Options/index.js b/src/Options/index.js index 79c46679c2..7997419db8 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -366,8 +366,7 @@ export interface ParseServerOptions { /* Callback when server has closed */ serverCloseComplete: ?() => void; /* Options to limit the complexity of requests to prevent denial-of-service attacks. Limits are enforced for all requests except those using the master or maintenance key. Each property can be set to `-1` to disable that specific limit. - :ENV: PARSE_SERVER_REQUEST_COMPLEXITY - :DEFAULT: {} */ + :ENV: PARSE_SERVER_REQUEST_COMPLEXITY */ requestComplexity: ?RequestComplexityOptions; /* The security options to identify and report weak security settings. :DEFAULT: {} */ diff --git a/src/Security/CheckGroups/CheckGroupServerConfig.js b/src/Security/CheckGroups/CheckGroupServerConfig.js index 36b327dc91..c9d8742559 100644 --- a/src/Security/CheckGroups/CheckGroupServerConfig.js +++ b/src/Security/CheckGroups/CheckGroupServerConfig.js @@ -143,7 +143,7 @@ class CheckGroupServerConfig extends CheckGroup { check: () => { const rc = config.requestComplexity; if (!rc) { - throw 1; + return; } const values = [rc.includeDepth, rc.includeCount, rc.subqueryDepth, rc.graphQLDepth, rc.graphQLFields]; if (values.some(v => v === -1)) {