Skip to content

fix: Denial-of-service via unbounded query complexity in REST and GraphQL API (GHSA-cmj3-wx7h-ffvg)#10130

Merged
mtrezza merged 12 commits intoparse-community:alphafrom
mtrezza:fix/GHSA-cmj3-wx7h-ffvg-v9
Mar 7, 2026
Merged

fix: Denial-of-service via unbounded query complexity in REST and GraphQL API (GHSA-cmj3-wx7h-ffvg)#10130
mtrezza merged 12 commits intoparse-community:alphafrom
mtrezza:fix/GHSA-cmj3-wx7h-ffvg-v9

Conversation

@mtrezza
Copy link
Member

@mtrezza mtrezza commented Mar 7, 2026

Pull Request

Issue

Denial-of-service via unbounded query complexity in REST and GraphQL API (GHSA-cmj3-wx7h-ffvg)

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

  • New Features

    • Configurable request complexity limits for GraphQL (depth/fields), includes (depth/count), and subqueries with sensible defaults and master/maintenance bypass.
  • Bug Fixes

    • Enforced include and subquery nesting checks during request handling to prevent excessive request complexity.
  • Tests

    • Added comprehensive tests for GraphQL complexity, include/subquery limits, and configuration validation; some tests adapt server config for coverage.
  • Documentation

    • Documented the new requestComplexity configuration options and defaults.
  • Security

    • Added a server check requiring request complexity limits to be enabled.

@parse-github-assistant
Copy link

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title fix: ghsa cmj3 wx7h ffvg v9 fix: Ghsa cmj3 wx7h ffvg v9 Mar 7, 2026
@parse-github-assistant
Copy link

parse-github-assistant bot commented Mar 7, 2026

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Mar 7, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds requestComplexity options, validation, and enforcement for GraphQL (depth/fields) and REST (include/subquery depth/count); integrates a GraphQL complexity validation plugin, updates option definitions/docs/config, adds a server config check, and adds tests covering these behaviors.

Changes

Cohort / File(s) Summary
Options / Definitions / Docs
resources/buildConfigDefinitions.js, src/Options/Definitions.js, src/Options/docs.js, src/Options/index.js
Introduce RequestComplexityOptions and ParseServerOptions.requestComplexity with env mappings and defaults (graphQLDepth, graphQLFields, includeCount, includeDepth, subqueryDepth).
Config Validation
src/Config.js
Import and validate requestComplexity via new validateRequestComplexity; integrate into overall option validation and apply defaults/constraints.
GraphQL server
src/GraphQL/ParseGraphQLServer.js, src/GraphQL/helpers/queryComplexity.js
Add calculateQueryComplexity and createComplexityValidationPlugin; register plugin to enforce GraphQL depth/field limits at request time (skips for master/maintenance).
REST query enforcement
src/RestQuery.js
Add validateIncludeComplexity and checkSubqueryDepth; propagate incremented _subqueryDepth via child contexts; invoke validations during include handling and subquery build/execution.
Security checks
src/Security/CheckGroups/CheckGroupServerConfig.js
Add "Request complexity limits enabled" check ensuring requestComplexity exists and none of its fields are -1.
Tests
spec/GraphQLQueryComplexity.spec.js, spec/RequestComplexity.spec.js, spec/RestQuery.spec.js, spec/SecurityCheckGroups.spec.js, spec/ParseGraphQLServer.spec.js
Add comprehensive tests for GraphQL and REST complexity limits, config validation, master/maintenance bypasses, and a few tests that reconfigure server requestComplexity mid-test.
Definitions build mapping
resources/buildConfigDefinitions.js
Map RequestComplexityOptions into nested option types and env-prefix resolution for config generation.

Sequence Diagram(s)

sequenceDiagram
    participant Client as GraphQL Client
    participant Server as ParseGraphQLServer
    participant Plugin as Complexity Plugin
    participant Validator as calculateQueryComplexity
    participant Executor as Apollo Executor
    Client->>Server: POST /graphql (query)
    Server->>Plugin: requestDidStart / didResolveOperation
    Plugin->>Plugin: Check auth (master/maintenance)
    alt Master or Maintenance Key
        Plugin-->>Server: Bypass validation
    else Standard Auth
        Plugin->>Validator: calculateQueryComplexity(operation, fragments)
        Validator->>Validator: Traverse selectionSet and fragments
        Validator-->>Plugin: { depth, fields }
        Plugin->>Plugin: Compare vs config limits
        alt Exceeds Limit
            Plugin->>Client: Throw GraphQLError (400)
        else Within Limit
            Plugin->>Executor: Allow execution
            Executor-->>Client: Query result
        end
    end
Loading
sequenceDiagram
    participant Client as REST Client
    participant RestQuery as RestQuery.execute()
    participant IncludeVal as validateIncludeComplexity
    participant SubqueryVal as checkSubqueryDepth
    participant Storage as Database/Subquery Execution
    Client->>RestQuery: REST request (includes / where with subqueries)
    RestQuery->>RestQuery: handleIncludeAll
    RestQuery->>IncludeVal: validateIncludeComplexity(context)
    IncludeVal->>IncludeVal: Check auth and count/depth vs config
    alt Exceeds Include Limits
        IncludeVal->>Client: Reject with error (400)
    else Within Limits
        IncludeVal-->>RestQuery: Continue
    end
    RestQuery->>SubqueryVal: checkSubqueryDepth(context)
    SubqueryVal->>SubqueryVal: Check auth and depth
    alt Exceeds Subquery Depth
        SubqueryVal->>Client: Reject with error (400)
    else Within Limits
        SubqueryVal-->>RestQuery: Create childContext(depth+1)
        RestQuery->>Storage: Execute subquery with childContext
        Storage-->>RestQuery: Subquery results
        RestQuery-->>Client: Response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: addressing a denial-of-service vulnerability via unbounded query complexity in both REST and GraphQL APIs, which is the primary focus of all the changes in the pull request.
Description check ✅ Passed The PR description is mostly complete with issue, approach, and tasks sections, though the Approach section lacks detailed implementation specifics.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
spec/GraphQLQueryComplexity.spec.js (1)

42-55: buildDeepQuery helper is defined but never used.

The buildDeepQuery function is defined to create deeply nested queries with synthetic fields (f0, f1, etc.), but none of the tests actually call it. The depth tests use inline query strings instead. Consider either using this helper in the depth tests or removing it to avoid dead code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/GraphQLQueryComplexity.spec.js` around lines 42 - 55, The helper
function buildDeepQuery is dead code; either remove it or make the depth tests
call it instead of using inline query strings. Edit the
spec/GraphQLQueryComplexity.spec.js tests that assert complexity for various
depths to call buildDeepQuery(depth) (using the same numeric depth values
currently inlined) so the tests reuse buildDeepQuery, or delete the
buildDeepQuery function if you prefer to keep the inline literals; update any
references to f0/f1-style synthetic fields accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@spec/RequestComplexity.spec.js`:
- Around line 107-129: The test is brittle because reconfigureServer() may reuse
shared test config from prior specs; ensure the config is reset before asserting
defaults by clearing or resetting the test configuration prior to calling
reconfigureServer({}) — e.g., call Config.reset('test') or a provided cleanup
helper (or add a beforeEach that resets Config and/or stops/clears the server)
so that reconfigureServer and Config.get('test') return true defaults; update
the spec to invoke that reset (or add the reset in beforeEach) before the
"should apply full defaults when not configured" assertion.

In `@spec/RestQuery.spec.js`:
- Line 393: The test sets a global server config by calling reconfigureServer({
requestComplexity: { includeCount: 200 } }) which can leak into later specs;
wrap this change so it's reverted in teardown by capturing the previous
requestComplexity (via reconfigureServer or reading current config) and
restoring it in an afterEach / finally block, or replace the direct call with a
scoped helper that temporarily applies the override for this spec only;
reference reconfigureServer, requestComplexity, includeCount, and ensure
restoration runs even on failures (use afterEach or a try/finally).

In `@src/GraphQL/helpers/queryComplexity.js`:
- Around line 33-37: calculateQueryComplexity is walking every
OperationDefinition in requestContext.document which wrongly scores operations
that weren't selected; change the logic to only score the resolved operation
from requestContext.didResolveOperation (use didResolveOperation.operationName
or the resolvedOperation node) and locate the matching OperationDefinition in
requestContext.document (or use the resolved operation directly) and call
visitSelectionSet on that operation's selectionSet only; apply the same fix to
the other loop that iterates definitions (the block around the second
occurrence) so unused operations are ignored.
- Line 8: Several single-line if statements lack braces (e.g., "if
(!selectionSet) return;" and the other brace-less ifs at the locations flagged)
which fails linting; update each of those statements in
src/GraphQL/helpers/queryComplexity.js to use block braces (for example: if
(!selectionSet) { return; }) so the if bodies are wrapped in {}—ensure this
change is applied to the instances around the selectionSet check and the other
two flagged conditions at the same file (lines flagged: 8, 23, 52, 55) so lint
passes.
- Around line 21-28: The bug is that visitedFragments is shared across sibling
branches so repeated uses of the same fragment in different siblings are
skipped; change the recursion to track visited fragments per traversal path
instead of globally by cloning the Set when descending into a sibling branch or
when recursing from a FragmentSpread. Concretely, inside visitSelectionSet (and
where you handle selection.kind === 'FragmentSpread') stop mutating a single
global visitedFragments; create a new Set(visitedFragments) and pass that cloned
set into the recursive visitSelectionSet calls (while still marking the fragment
in the cloned set to avoid infinite recursion). This preserves loop protection
for recursion but allows the same fragment to be counted separately when reused
in different branches.

In `@src/Options/index.js`:
- Around line 428-433: The graphQLDepth and graphQLFields option entries lack
explicit :ENV: annotations so the generator creates awkward
PARSE_SERVER_REQUEST_COMPLEXITY_GRAPH_QLDEPTH/…_GRAPH_QLFIELDS names; add
explicit environment-variable names to those options in src/Options/index.js by
annotating graphQLDepth with ":ENV: GRAPHQL_DEPTH" and graphQLFields with ":ENV:
GRAPHQL_FIELDS" (so generated vars become PARSE_SERVER_GRAPHQL_DEPTH /
PARSE_SERVER_GRAPHQL_FIELDS), then run "npm run definitions" to propagate
changes to Definitions.js and docs.js.

---

Nitpick comments:
In `@spec/GraphQLQueryComplexity.spec.js`:
- Around line 42-55: The helper function buildDeepQuery is dead code; either
remove it or make the depth tests call it instead of using inline query strings.
Edit the spec/GraphQLQueryComplexity.spec.js tests that assert complexity for
various depths to call buildDeepQuery(depth) (using the same numeric depth
values currently inlined) so the tests reuse buildDeepQuery, or delete the
buildDeepQuery function if you prefer to keep the inline literals; update any
references to f0/f1-style synthetic fields accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 89c7f221-8568-4608-b808-7d806ec1be49

📥 Commits

Reviewing files that changed from the base of the PR and between 3ed96d3 and 88fa3aa.

📒 Files selected for processing (11)
  • resources/buildConfigDefinitions.js
  • spec/GraphQLQueryComplexity.spec.js
  • spec/RequestComplexity.spec.js
  • spec/RestQuery.spec.js
  • src/Config.js
  • src/GraphQL/ParseGraphQLServer.js
  • src/GraphQL/helpers/queryComplexity.js
  • src/Options/Definitions.js
  • src/Options/docs.js
  • src/Options/index.js
  • src/RestQuery.js

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/GraphQL/helpers/queryComplexity.js (2)

23-31: ⚠️ Potential issue | 🔴 Critical

Count repeated fragments per traversal path, not globally.

visitedFragments is still shared across sibling branches, so reusing the same fragment under different fields is only charged once. That undercounts wide queries and weakens the DoS protection this feature is meant to add.

Proposed fix
       } else if (selection.kind === 'FragmentSpread') {
         const name = selection.name.value;
         if (visitedFragments.has(name)) {
           continue;
         }
-        visitedFragments.add(name);
         const fragment = fragments[name];
         if (fragment) {
-          visitSelectionSet(fragment.selectionSet, depth, visitedFragments);
+          const nextVisitedFragments = new Set(visitedFragments);
+          nextVisitedFragments.add(name);
+          visitSelectionSet(fragment.selectionSet, depth, nextVisitedFragments);
         }
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/GraphQL/helpers/queryComplexity.js` around lines 23 - 31, The bug is that
visitedFragments is shared across sibling traversal paths causing fragments to
be counted only once globally; in visitSelectionSet (and specifically where
FragmentSpread is handled) pass a per-path copy of visitedFragments instead of
the shared set—e.g., replace calls like visitSelectionSet(fragment.selectionSet,
depth, visitedFragments) with visitSelectionSet(fragment.selectionSet, depth,
new Set(visitedFragments)) so each traversal path tracks fragment visits
independently (also apply the same cloning approach when recursing into sibling
field/inline fragment branches in visitSelectionSet).

72-75: ⚠️ Potential issue | 🟠 Major

Only score the resolved operation.

didResolveOperation has already selected the operation that will execute, but this still passes the full document into calculateQueryComplexity(). A request with multiple operations can therefore be rejected because of an unused operation.

Proposed fix
         const { depth, fields } = calculateQueryComplexity(
-          requestContext.document,
+          { definitions: [requestContext.operation] },
           fragments
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/GraphQL/helpers/queryComplexity.js` around lines 72 - 75, The code is
passing the whole GraphQL document into calculateQueryComplexity causing unused
operations to be scored; in the didResolveOperation handler replace
requestContext.document with the resolved operation AST
(requestContext.operation) so calculateQueryComplexity(requestContext.operation,
fragments) computes complexity only for the selected operation (keep fragments
as-is).
🧹 Nitpick comments (2)
spec/SecurityCheckGroups.spec.js (1)

44-51: Avoid asserting security checks by array index.

Adding this check already shifted the numbering. Looking up the check by identity/title instead of position will keep these tests stable when more checks are inserted or reordered.

Also applies to: 74-81

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@spec/SecurityCheckGroups.spec.js` around lines 44 - 51, Replace fragile
index-based assertions in Spec file by finding checks by a stable identifier
(e.g., title/name/id) and asserting their checkState equals CheckState.success;
for example, use group.checks().find(c => c.title === '...') or similar instead
of group.checks()[N].checkState() for all occurrences (the block with multiple
expects using group.checks()[0], [1], [2], [4], [5], [6], [8], [9] and the
similar block at lines 74-81), and update the test to throw a clear failure if
the named check is not found before asserting its state.
src/Security/CheckGroups/CheckGroupServerConfig.js (1)

148-149: Keep the checked keys in sync with the shared option definition.

This list is duplicated here, so a future RequestComplexityOptions addition can ship without security-check coverage and still report success. Consider deriving the keys from the shared option surface instead of hardcoding them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Security/CheckGroups/CheckGroupServerConfig.js` around lines 148 - 149,
The check currently hardcodes RequestComplexity option keys (rc.includeDepth,
rc.includeCount, rc.subqueryDepth, rc.graphQLDepth, rc.graphQLFields) which can
drift from the shared RequestComplexityOptions; update the logic to derive the
keys from the shared option definition (e.g., import or reference
RequestComplexityOptions or the shared options object used elsewhere) and map
those keys to rc[key] for the existence/`-1` check (replace the values array
creation with something like Object.keys(RequestComplexityOptions).map(k =>
rc[k]) so future additions to RequestComplexityOptions are automatically
covered).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/GraphQL/helpers/queryComplexity.js`:
- Around line 23-31: The bug is that visitedFragments is shared across sibling
traversal paths causing fragments to be counted only once globally; in
visitSelectionSet (and specifically where FragmentSpread is handled) pass a
per-path copy of visitedFragments instead of the shared set—e.g., replace calls
like visitSelectionSet(fragment.selectionSet, depth, visitedFragments) with
visitSelectionSet(fragment.selectionSet, depth, new Set(visitedFragments)) so
each traversal path tracks fragment visits independently (also apply the same
cloning approach when recursing into sibling field/inline fragment branches in
visitSelectionSet).
- Around line 72-75: The code is passing the whole GraphQL document into
calculateQueryComplexity causing unused operations to be scored; in the
didResolveOperation handler replace requestContext.document with the resolved
operation AST (requestContext.operation) so
calculateQueryComplexity(requestContext.operation, fragments) computes
complexity only for the selected operation (keep fragments as-is).

---

Nitpick comments:
In `@spec/SecurityCheckGroups.spec.js`:
- Around line 44-51: Replace fragile index-based assertions in Spec file by
finding checks by a stable identifier (e.g., title/name/id) and asserting their
checkState equals CheckState.success; for example, use group.checks().find(c =>
c.title === '...') or similar instead of group.checks()[N].checkState() for all
occurrences (the block with multiple expects using group.checks()[0], [1], [2],
[4], [5], [6], [8], [9] and the similar block at lines 74-81), and update the
test to throw a clear failure if the named check is not found before asserting
its state.

In `@src/Security/CheckGroups/CheckGroupServerConfig.js`:
- Around line 148-149: The check currently hardcodes RequestComplexity option
keys (rc.includeDepth, rc.includeCount, rc.subqueryDepth, rc.graphQLDepth,
rc.graphQLFields) which can drift from the shared RequestComplexityOptions;
update the logic to derive the keys from the shared option definition (e.g.,
import or reference RequestComplexityOptions or the shared options object used
elsewhere) and map those keys to rc[key] for the existence/`-1` check (replace
the values array creation with something like
Object.keys(RequestComplexityOptions).map(k => rc[k]) so future additions to
RequestComplexityOptions are automatically covered).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 66973983-2f51-43d7-8744-731c7fafe5c4

📥 Commits

Reviewing files that changed from the base of the PR and between 88fa3aa and 0078273.

📒 Files selected for processing (4)
  • spec/GraphQLQueryComplexity.spec.js
  • spec/SecurityCheckGroups.spec.js
  • src/GraphQL/helpers/queryComplexity.js
  • src/Security/CheckGroups/CheckGroupServerConfig.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • spec/GraphQLQueryComplexity.spec.js

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 7, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@spec/ParseGraphQLServer.spec.js`:
- Around line 9557-9566: After calling global.reconfigureServer and creating the
new ParseGraphQLServer instance, remount the fresh Parse Server instance so the
HTTP server the test client hits is replaced; specifically, ensure the old HTTP
server started in the outer beforeEach is stopped and the new parseServer (used
to construct parseGraphQLServer) is started/mounted (so requestComplexity: {
includeDepth: 10 } actually applies). Locate the reconfigureServer call and the
ParseGraphQLServer construction (parseServer, new ParseGraphQLServer(...)) and
add the appropriate teardown/start or helper call to replace the running HTTP
server with the newly configured parseServer before running the assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5547e2c5-a1c1-4856-8f32-313463001ad9

📥 Commits

Reviewing files that changed from the base of the PR and between 059c1a6 and ca6659d.

📒 Files selected for processing (1)
  • spec/ParseGraphQLServer.spec.js

@mtrezza
Copy link
Member Author

mtrezza commented Mar 7, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@codecov
Copy link

codecov bot commented Mar 7, 2026

Codecov Report

❌ Patch coverage is 93.57798% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.71%. Comparing base (b2f2317) to head (71c1a9f).
⚠️ Report is 3 commits behind head on alpha.

Files with missing lines Patch % Lines
src/GraphQL/helpers/queryComplexity.js 92.15% 4 Missing ⚠️
src/Config.js 93.33% 1 Missing ⚠️
src/RestQuery.js 97.05% 1 Missing ⚠️
src/Security/CheckGroups/CheckGroupServerConfig.js 83.33% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##            alpha   #10130    +/-   ##
========================================
  Coverage   92.70%   92.71%            
========================================
  Files         191      192     +1     
  Lines       15942    16051   +109     
  Branches      180      180            
========================================
+ Hits        14779    14881   +102     
- Misses       1151     1158     +7     
  Partials       12       12            

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/RestQuery.js (1)

890-911: Implementation is correct, but note a potential user expectation mismatch.

The includeCount check validates against this.include.length, which includes all intermediate paths. For example, include=a.b.c is expanded internally to [['a'], ['a', 'b'], ['a', 'b', 'c']], counting as 3 includes rather than 1. This is technically correct since all intermediate pointers are resolved, but users specifying include=a.b.c,x.y.z might be surprised that it counts as 6 includes.

Consider whether the documentation for includeCount clarifies this behavior, or if the count should be based on user-specified paths instead of expanded paths.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/RestQuery.js` around lines 890 - 911, The includeCount check currently
counts expanded intermediate include paths (this.include), causing
user-surprise; change the check in
_UnsafeRestQuery.prototype.validateIncludeComplexity to count user-intended
include paths by deriving the set of maximal (non-prefix) paths from
this.include before comparing to rc.includeCount—i.e., filter this.include to
only keep paths that are not a prefix of any other path (join arrays to compare
or compare lengths/element-wise), use that filtered length for the includeCount
comparison and messages, and keep logging/throwing the same Parse.Error when
exceeded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/RestQuery.js`:
- Around line 890-911: The includeCount check currently counts expanded
intermediate include paths (this.include), causing user-surprise; change the
check in _UnsafeRestQuery.prototype.validateIncludeComplexity to count
user-intended include paths by deriving the set of maximal (non-prefix) paths
from this.include before comparing to rc.includeCount—i.e., filter this.include
to only keep paths that are not a prefix of any other path (join arrays to
compare or compare lengths/element-wise), use that filtered length for the
includeCount comparison and messages, and keep logging/throwing the same
Parse.Error when exceeded.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 96c24ae1-b3d3-48ac-8857-5891e5fc6e40

📥 Commits

Reviewing files that changed from the base of the PR and between ca6659d and c3cc9e4.

📒 Files selected for processing (3)
  • spec/ParseGraphQLServer.spec.js
  • src/GraphQL/helpers/queryComplexity.js
  • src/RestQuery.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/GraphQL/helpers/queryComplexity.js

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 7, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/RestQuery.js`:
- Around line 890-910: In validateIncludeComplexity (method
_UnsafeRestQuery.prototype.validateIncludeComplexity) deduplicate this.include
before enforcing rc.includeCount to avoid false rejections from
overlapping/duplicate paths (handleInclude and handleIncludeAll can leave
duplicates); create a Set of serialized include paths (e.g., serialize each
include entry consistently with path.join('.') or JSON.stringify(path)) and use
that Set.size for the includeCount check (and optionally for any subsequent
include-count-based logic), leaving this.include unchanged.
- Around line 461-475: The checkSubqueryDepth logic (in
_UnsafeRestQuery.prototype.checkSubqueryDepth) and the other places noted should
stop reading/writing context._subqueryDepth and instead use a private RestQuery
instance field (e.g., this._subqueryDepth) to track nesting; update all reads,
increments and resets (the occurrences referenced around the other check sites)
to use this internal property, ensure the auth checks and config use remain the
same, and do not expose or mutate the request context object so triggers still
receive the original context untouched.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e540ec82-38d0-4cae-9e9d-cdfce167e98f

📥 Commits

Reviewing files that changed from the base of the PR and between ca6659d and 6b608c2.

📒 Files selected for processing (4)
  • spec/GraphQLQueryComplexity.spec.js
  • spec/ParseGraphQLServer.spec.js
  • src/GraphQL/helpers/queryComplexity.js
  • src/RestQuery.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/GraphQL/helpers/queryComplexity.js
  • spec/ParseGraphQLServer.spec.js

coderabbitai[bot]
coderabbitai bot previously approved these changes Mar 7, 2026
@mtrezza mtrezza changed the title fix: Ghsa cmj3 wx7h ffvg v9 fix: Denial-of-service via unbounded query complexity in REST and GraphQL API (GHSA-cmj3-wx7h-ffvg) Mar 7, 2026
@mtrezza mtrezza merged commit 0ae9c25 into parse-community:alpha Mar 7, 2026
23 of 24 checks passed
parseplatformorg pushed a commit that referenced this pull request Mar 7, 2026
## [9.5.2-alpha.2](9.5.2-alpha.1...9.5.2-alpha.2) (2026-03-07)

### Bug Fixes

* Denial-of-service via unbounded query complexity in REST and GraphQL API ([GHSA-cmj3-wx7h-ffvg](https://github.com/parse-community/parse-server/security/advisories/GHSA-cmj3-wx7h-ffvg)) ([#10130](#10130)) ([0ae9c25](0ae9c25))
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 9.5.2-alpha.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:released-alpha Released as alpha version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants