parse-server icon indicating copy to clipboard operation
parse-server copied to clipboard

Set default values for max depth and max fields

Open mtrezza opened this issue 1 month ago • 6 comments

New Feature / Enhancement Checklist

Current Limitation

https://github.com/parse-community/parse-server/pull/9920 introduced a max depth and max fields protection system that is optional and disabled by default.

Feature / Enhancement Description

Enable by default by adding default values for https://github.com/parse-community/parse-server/pull/9920.

mtrezza avatar Nov 09 '25 19:11 mtrezza

🚀 Thanks for opening this issue! We are excited about your ideas for improvement!

Some default values could be

 maxGraphQLQueryComplexity: {
          depth: 50,
          fields: 250,
        },
maxIncludeQueryComplexity: {
          depth: 10,
          count: 50,
        },

Note the x5 factor on the depth, it will may be need adjustments depending of developers feedback

Moumouls avatar Nov 09 '25 19:11 Moumouls

This can be merged for PS9, with the note in the changelog that it also disables Parse.Query.includeAll by default. See also https://github.com/parse-community/parse-server/issues/9939#issuecomment-3566667002.

mtrezza avatar Nov 22 '25 12:11 mtrezza

@coderabbitai suggest good practical default values for maxGraphQLQueryComplexity and maxIncludeQueryComplexity.

mtrezza avatar Nov 22 '25 12:11 mtrezza

[!TIP] For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's find where these configuration options are defined
echo "=== Searching for maxGraphQLQueryComplexity configuration ==="
rg -n -C5 "maxGraphQLQueryComplexity"

echo -e "\n=== Searching for maxIncludeQueryComplexity configuration ==="
rg -n -C5 "maxIncludeQueryComplexity"

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Look at the Options/index.js to understand the option definitions
echo "=== Checking Options/index.js ==="
cat src/Options/index.js | grep -A 20 -B 2 "maxGraphQLQueryComplexity\|maxIncludeQueryComplexity"

Length of output: 33


🏁 Script executed:

#!/bin/bash
# Let's check PR #9920 and issue #9928 using GitHub CLI
echo "=== Getting PR #9920 details ==="
gh pr view 9920 --json title,body,files --repo parse-community/parse-server

echo -e "\n=== Getting issue #9928 details ==="
gh issue view 9928 --json title,body,comments --repo parse-community/parse-server

Length of output: 6180


🏁 Script executed:

#!/bin/bash
# Look for the actual implementation of these complexity checks
echo "=== Finding complexity check implementations ==="
rg -n -B3 -A10 "complexity.*depth|depth.*complexity" --type js | head -100

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Let's check out the files from PR #9920 to understand the implementation
echo "=== Checking queryComplexity helper implementation ==="
fd -t f "queryComplexity.js" --exec cat {}

echo -e "\n=== Checking RestQuery implementation ==="
rg -n "maxIncludeQueryComplexity" src/RestQuery.js -A20 -B5

echo -e "\n=== Checking GraphQL complexity implementation ==="
fd -t f "ParseGraphQLServer.js" --exec rg -n "maxGraphQLQueryComplexity" {} -A20 -B5

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Let's look at the spec files to understand how these limits work
echo "=== Checking RestQuery spec for complexity tests ==="
rg -n "depth.*count|maxIncludeQueryComplexity" spec/RestQuery.spec.js -B2 -A5 | head -80

echo -e "\n=== Checking GraphQL spec for complexity tests ==="
rg -n "depth.*fields|maxGraphQLQueryComplexity" spec/ParseGraphQLQueryComplexity.spec.js -B2 -A5 | head -80

Length of output: 185


🏁 Script executed:

#!/bin/bash
# Let's check the Options index to see the option definitions
echo "=== Looking at Options/index.js structure ==="
cat src/Options/index.js | head -200

Length of output: 15579


🏁 Script executed:

#!/bin/bash
# Let's check the current branch and see if we're on the PR branch
echo "=== Current branch info ==="
git branch --show-current
git log --oneline -5

echo -e "\n=== Looking for any complexity-related files ==="
fd -t f "complexity" --exec ls -la {}

Length of output: 441


🏁 Script executed:

#!/bin/bash
# Let's check if there are any existing specs or documentation about these features
echo "=== Searching for include depth or query depth mentions ==="
rg -i "include.*depth|query.*depth|includeAll" --type js -n | head -50

echo -e "\n=== Searching for any existing complexity or performance limits ==="
rg -i "complexity|maxDepth|max.*query" src/Options/index.js -n -C3

Length of output: 1662


🌐 Web query:

GraphQL query depth and field complexity limits best practices

💡 Result:

Brief best-practices summary for query depth and field-complexity limits

  1. Use depth limits as a basic guardrail

    • Enforce a maximum query depth (common starting values: 5–10; introspection may need >10). Tune by observing real queries. [1][3][8]
  2. Add query complexity / cost analysis (field weighting)

    • Assign costs to fields (higher for lists, expensive resolvers, remote joins) and reject queries whose total cost exceeds a budget. This catches expensive shallow queries that depth limits miss. [1][2][7]
  3. Limit list / nested-list depth separately

    • Lists nested inside lists produce exponential results; apply a smaller limit for list nesting or add larger costs to list-yielding fields. [1][7]
  4. Protect against recursive/self-referential cycles

    • Set a low max-self-referential depth and allow per-field overrides for known safe recursive fields. [3]
  5. Combine with other controls

    • Resolver-count / field-breadth limits, rate limiting, auth/authorization, and persisted (whitelisted) queries reduce attack surface. [1][5][8]
  6. Provide explicit overrides and monitoring

    • Allow safe exceptions for known legitimate queries (per-field or per-role), but log rejected/near-limit queries and adjust thresholds based on metrics. [3][1]
  7. Optimize resolvers to reduce cost

    • Use batching (DataLoader), proper pagination (cursor-based), caching and DB query tuning so legitimate queries are cheaper. Complexity rules work best when backed by efficient resolvers. [1][4]
  8. Fail fast and return clear errors

    • Validate and reject too-deep / too-costly queries before execution so resources aren’t wasted; surface clear error messages so clients can fix queries. [1][9]

Implementation notes / starting config

  • Start with a conservative depth (e.g., 5–10) and a modest cost budget; measure rejected and costly queries and iterate. [3][8]
  • Use proven libraries or platform features: graphql-depth-limit / graphile depth-limit, graphql-cost-analysis, or your GraphQL server/platform’s built-in settings (e.g., AppSync, async-graphql). [3][7][9]

Sources [1] GraphQL.org — Security / Going to production guidance.
[2] Apollo GraphQL blog — Securing your GraphQL API (depth & cost discussion).
[3] graphile/depth-limit (GitHub) — options & recommended defaults.
[4] AWS AppSync docs/blog — query depth, resolver count features.
[5] async-graphql docs — limit_depth and complexity controls.

If you want, I can recommend specific numeric defaults tuned to your schema and traffic (I’ll need sample queries, resolver cost estimates, and typical client use-cases).


🌐 Web query:

REST API include query depth limits security performance

💡 Result:

Short answer: Yes — enforce limits on include/expand/query depth for REST endpoints that support nested expansion. Depth limits prevent resource exhaustion, reduce latency and DB load, and close an easy attack/abuse vector; they’re a common, recommended protection (and are widely implemented in OData/JSON:API/DRF tooling and in GraphQL guidance). [1][2][3][4]

What to enforce (recommended minimum set)

  • Max expansion depth: a global default (e.g. 2–5) and optional per-endpoint override. [3][2]
  • Recursive/loop prevention: reject or truncate recursive include patterns. [2]
  • Max items / page size per expansion (cap lists returned by expanded relations). [4]
  • Complexity/weighting: assign cost to expensive fields/expansions and reject high-cost requests. [4]
  • Rate limits + quotas per client to limit repeated expensive calls. [1][4]
  • Whitelists for allowed includes (only expose safe relations). [2]

Why (security & performance)

  • Prevents DoS/resource exhaustion from deeply nested or exponential expansions. [1][4]
  • Limits expensive DB joins / N+1 problems and large result serialization. [4]
  • Reduces attacker ability to discover/internal graph shape via repeated deep requests. [1]

Implementation patterns / controls

  • Server-side validation: parse include/expand string and calculate depth before resolving; return 4xx if exceeded. [2][3]
  • Per-field cost model + complexity budget checked at request time. [4]
  • Per-endpoint sensible defaults + admin-configurable maxima. [3]
  • Combine with caching, pagination, and database-side safeguards (timeouts, query plans). [4]

References

  • GraphQL security / depth & complexity guidance. [1]
  • drf-flex-fields (expand param + MAXIMUM_EXPANSION_DEPTH option). [2]
  • Microsoft OData MaxExpansionDepth (EnableQuery / ODataValidationSettings). [3]
  • Apollo / AWS docs on query complexity, depth limiting and resolver limits. [4]

If you want, tell me which stack/framework you use and I’ll give a concrete config/code example.


@mtrezza, based on industry best practices for GraphQL and REST API complexity limits, here are my recommendations:

Recommended Default Values

For REST API (maxIncludeQueryComplexity)

{
  depth: 10,
  count: 100
}

For GraphQL (maxGraphQLQueryComplexity)

{
  depth: 15,
  fields: 200
}

Rationale

Why GraphQL can have deeper nesting:

  1. Explicit field selection - GraphQL queries explicitly specify every field at each level, providing granular control
  2. Validated before execution - The entire query structure is validated upfront
  3. No implicit data fetching - Only requested fields are fetched, not entire objects

Why REST includes need lower depth:

  1. Implicit object fetching - Each include level fetches entire objects, not just specific fields
  2. Less predictable load - Harder to estimate the actual data volume from include syntax alone
  3. Legacy considerations - Many Parse apps use includes without careful optimization

The multiplier difference (1.5x instead of 5x):

While Moumouls' suggestion of 5x (depth 50 vs 10) is conservative, I recommend a smaller ratio because:

  • Depth 50 in GraphQL is extremely deep and may indicate a schema design issue
  • Depth 15-20 covers legitimate use cases while still protecting against abuse
  • The field count (200) is the primary protection in GraphQL, as breadth is often more expensive than depth

Count/Fields values:

  • REST count: 100 - Allows complex queries with multiple include paths (e.g., include=user,comments.author,likes.user)
  • GraphQL fields: 200 - Permits reasonable queries while preventing field enumeration attacks

Additional Considerations

  1. Master/Maintenance keys bypass these limits (as per PR #9920), so internal operations remain unrestricted
  2. Parse.Query.includeAll will be blocked by default, which is a good security improvement
  3. These can be increased per deployment based on monitoring and specific use cases
  4. Start conservative - It's easier to increase limits than decrease them after apps depend on higher limits

Would you like me to adjust these recommendations based on any specific Parse Server usage patterns you've observed?


🧠 Learnings used
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-08T13:46:04.940Z
Learning: When reviewing Parse Server PRs that add new features, always check whether the feature is documented in the README.md file, though for new Parse Server options this is optional rather than required.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-08T13:46:04.940Z
Learning: For new Parse Server options, verify that the option is documented in src/Options/index.js and that npm run definitions has been executed to reflect changes in src/Options/docs.js and src/Options/Definitions.js. README.md documentation is a bonus but not required for new options.

coderabbitai[bot] avatar Nov 22 '25 12:11 coderabbitai[bot]

[!TIP] For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the options definitions for these complexity settings
rg -n "maxGraphQLQueryComplexity|maxIncludeQueryComplexity" --type js

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at the Options/index.js file for the option definitions
cat src/Options/index.js | grep -A 20 "maxGraphQLQueryComplexity\|maxIncludeQueryComplexity"

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the implementation files for these options
ast-grep --pattern 'maxGraphQLQueryComplexity'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for any test files that might show usage examples
rg -n "maxGraphQLQueryComplexity|maxIncludeQueryComplexity" spec/

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the PR changes - look at recent commits or implementation files
fd -e js | xargs rg -l "depth.*count.*complexity" | head -10

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Get details of PR #9920 to understand what was implemented
gh pr view 9920 --json title,body,state,mergedAt --repo parse-community/parse-server

Length of output: 2367


🏁 Script executed:

#!/bin/bash
# Search for query depth or field limits in the codebase
rg -n "depth.*limit|field.*limit|query.*depth|query.*field" --type js -g '!spec/' -g '!node_modules/' | head -20

Length of output: 1836


🏁 Script executed:

#!/bin/bash
# Look for GraphQL query complexity or include query complexity
rg -n "GraphQL.*complexity|include.*complexity|query complexity" --type js | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for includeAll functionality
rg -n "includeAll" --type js -C 3 | head -30

Length of output: 1326


🏁 Script executed:

#!/bin/bash
# Look for files dealing with GraphQL queries
fd graphql --type f -e js | head -10

Length of output: 365


🏁 Script executed:

#!/bin/bash
# Get the list of files changed in PR #9920
gh pr view 9920 --json files --jq '.files[].path' --repo parse-community/parse-server

Length of output: 260


🏁 Script executed:

#!/bin/bash
# Get the diff of the PR to understand the implementation
gh pr diff 9920 --repo parse-community/parse-server | head -500

Length of output: 12795


🏁 Script executed:

#!/bin/bash
# Look at issue #9928 to understand the context better
gh issue view 9928 --json title,body,comments --repo parse-community/parse-server

Length of output: 3097


🏁 Script executed:

#!/bin/bash
# Get more of the PR diff to see the implementation details
gh pr diff 9920 --repo parse-community/parse-server | grep -A 30 -B 5 "maxIncludeQueryComplexity\|maxGraphQLQueryComplexity" | head -200

Length of output: 5132


🏁 Script executed:

#!/bin/bash
# Look at the RestQuery spec to understand include complexity
gh pr diff 9920 --repo parse-community/parse-server | grep -A 100 "RestQuery.spec.js" | head -300

Length of output: 3183


🏁 Script executed:

#!/bin/bash
# Check issue #9939 for more context about includeAll
gh issue view 9939 --json title,body,comments --repo parse-community/parse-server

Length of output: 2433


🏁 Script executed:

#!/bin/bash
# Look at the Options/index.js to see option definitions
gh pr diff 9920 --repo parse-community/parse-server | grep -A 50 "Options/index.js" | head -200

Length of output: 2331


🏁 Script executed:

#!/bin/bash
# Check what typical GraphQL query depth patterns look like in the codebase
gh pr diff 9920 --repo parse-community/parse-server | grep -B 5 -A 10 "depth:" | head -100

Length of output: 2637


🏁 Script executed:

#!/bin/bash
# Look for any documentation or comments about recommended values
gh pr diff 9920 --repo parse-community/parse-server | grep -i "default\|recommend\|practical" | head -50

Length of output: 3714


🌐 Web query:

GraphQL query depth limit best practices recommendations

💡 Result:

Concise best-practices for GraphQL query depth limits

  • Pick a sensible global max depth (common starting range: 5–10). Keep it low for public APIs; raise only when justified. [1][2]
  • Remember introspection and Relay-style schemas can require deeper nesting (~12); allow exceptions or higher default if you need introspection. [3][4]
  • Use per-field or per-type overrides for legitimate recursive fields (e.g., User.friends) instead of a single blunt limit. [4]
  • Don’t rely on depth alone — combine depth limits with:
    • query complexity/cost analysis (field weights),
    • rate limiting / quotas,
    • execution timeouts, and
    • pagination/batch limits. [1][2][5]
  • Enforce limits as validation rules or instrumentation (use existing libraries/tools: graphql-depth-limit, GraphQL server instrumentations, AWS/AppSync settings). [4][5]
  • Protect schema discovery in untrusted environments (restrict or gate introspection) and whitelist known-safe queries for high-privilege clients. [1][3]
  • Log and monitor rejected/near-limit queries; tune limits from real traffic and add targeted exceptions where needed. [2][3]

References [1] GraphQL Security / Best Practices (graphql.org).
[2] Apollo: Securing Your GraphQL API (Apollo blog).
[3] graphile/depth-limit docs (defaults, introspection note).
[4] OWASP GraphQL Cheat Sheet / WSTG (query limiting recommendations).
[5] AWS AppSync — Query depth configuration.


🌐 Web query:

REST API include depth security recommendations

💡 Result:

Here are concise, practical security recommendations when you expose an "include"/"depth" parameter (or allow nested resource inclusion) on a REST API:

  1. Enforce a maximum include/depth limit (and default to a small value). Prevent deeply nested requests that exhaust CPU/memory. [1][2]

  2. Whitelist allowed relations and fields per endpoint (no generic include of everything). Return only fields the caller is authorized to see. [3][4]

  3. Apply object‑ and property‑level authorization for every included item/field (do not rely on endpoint auth only). Check permissions per object/field before including. [3][5]

  4. Apply request complexity controls (cost/weight scoring) and rate limits per token/IP to mitigate abuse. Reject requests above configured complexity. [2][6]

  5. Validate and schema‑check the include parameter (allowlist values, max length, no recursion/cycles). Treat it as untrusted input. [6][4]

  6. Strip or redact sensitive properties from included resources at the server side (PII, secrets, internal flags). Don’t rely on clients to filter. [3]

  7. Limit response size and set timeouts; paginate collections inside includes. Enforce content length and result count caps. [2][6]

  8. Log include requests and monitor anomalies (high depth, unusual combinations) for alerting and forensic tracing. [2][5]

  9. Use an API gateway/WAF to enforce depth/size/throttling rules centrally and to rate‑limit abusive callers. [2][6]

  10. Document the include semantics, limits, and security model; version changes that alter what is includable. Keep a maintained inventory of endpoints. [3][5]

References

  • [1] OWASP API Security — Excessive Data Exposure / API Security Top 10 (recommend controlling responses and property‑level exposure).
  • [2] Guidance on depth/complexity limiting & DoS prevention (GraphQL/REST depth limiting, complexity scoring).
  • [3] OWASP API Security Top‑10 (2023): Broken Object/Property Level Authorization, Unrestricted Resource Consumption, Logging/Monitoring.
  • [4] Practical depth‑limiting and input validation guidance (articles on depth limit implementations).
  • [5] REST API security best‑practices: input validation, authorization, logging.
  • [6] Rate limiting, throttling, and gateway controls for APIs.

If you want, I can: (a) propose concrete depth/complexity defaults (example values), (b) draft a whitelist schema and validation rules, or (c) provide sample middleware code for your platform (Node/Express, Java/Spring, or .NET). Which would help most?


🌐 Web query:

GraphQL field selection limit best practices

💡 Result:

Concise best-practices for limiting GraphQL field selection (to prevent over‑fetching, DoS and expensive resolvers):

  • Enforce a maximum query depth (start ~5–10; tune to your schema). Use graphql-depth-limit or similar. [1][2]
  • Add cost/complexity scoring per field (account for lists, expensive resolvers, and arguments) and reject queries above a budget. Use graphql-query-complexity or server-side cost analysis. [1][3]
  • Limit list sizes and pagination defaults (max page size, require cursor-based paging) so a single query can’t return huge result sets. [4][5]
  • Use persisted/allow‑listed queries for public APIs (or only accept trusted documents in production) to avoid arbitrary client-crafted queries. [6][1]
  • Combine depth/complexity checks with rate limiting, per-client quotas, and per-request timeouts to mitigate abuse. [4][5]
  • Instrument and log rejected/expensive queries, monitor trends, and adjust limits based on real traffic and developer needs. [7][1]
  • Apply field-level auth/guards so expensive or sensitive fields are accessible only to authorized/trusted callers. [4][3]

If you want, I can suggest concrete settings (depth, cost budgets, page sizes) tailored to your schema and traffic patterns.

Sources: [1] GraphQL.org (going to production / operation complexity). [2] Apollo blog (securing GraphQL). [3] WSO2 / query complexity docs. [4] graphql-js operation complexity controls. [5] Community guidance on depth/size limits. [6] Best practices re: persisted queries and trusted documents. [7] Monitoring/logging recommendations.


@mtrezza Based on my analysis of PR #9920, web best practices, and the Parse Server GraphQL schema structure, here are my recommendations for practical default values:

Recommended Defaults

maxGraphQLQueryComplexity

{
  depth: 20,
  fields: 200
}

Rationale:

  • Depth 20: Parse Server's GraphQL uses a Relay-style schema where basic queries like users { edges { node { ... } } } already consume 3-4 levels just to reach the data. A depth of 20 allows for this overhead plus 4-5 levels of meaningful nested relationships (e.g., user → posts → comments → author), which covers most real-world use cases while preventing deep recursion attacks.
  • Fields 200: This allows developers to select many fields across their query without hitting limits in normal usage, but prevents enormous queries that could cause DoS. Based on industry best practices, 200 fields is generous enough for complex dashboards/reports while still providing protection.

maxIncludeQueryComplexity

{
  depth: 10,
  count: 50
}

Rationale:

  • Depth 10: REST include queries use simpler pointer chains (e.g., comment.post.author.profile = depth 4). A depth of 10 allows for reasonable relationship traversal while being more restrictive than GraphQL, which aligns with security best practices for REST APIs.
  • Count 50: This limits the number of different include paths (e.g., include=foo,bar,baz = 3 paths). 50 is sufficient for complex queries that need to fetch multiple related objects while preventing abuse.

Comparison with Other Suggestions

Option Moumouls Original PR My Recommendation
GraphQL depth 50 20 20
GraphQL fields 250 200 200
Include depth 10 10 10
Include count 50 100 50

My recommendations lean towards the more conservative values from the original PR suggestions, as they still provide ample room for legitimate use cases while offering better security posture. The values can be increased in future versions if developer feedback indicates they're too restrictive.

Important Notes

  1. Breaking Change Impact: These defaults will disable Parse.Query.includeAll by default for non-master users (as discussed in #9939)
  2. Master/Maintenance Key Bypass: These limits don't apply when using master or maintenance keys
  3. Monitoring Recommended: After enabling these defaults, monitor rejected queries and gather developer feedback to tune the values if needed
  4. Cross-validation: The implementation ensures maxIncludeQueryComplexity values are lower than maxGraphQLQueryComplexity values to avoid conflicts

Would you like me to adjust these recommendations based on any specific Parse Server usage patterns or concerns?


🧠 Learnings used
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-08T13:46:04.940Z
Learning: When reviewing Parse Server PRs that add new features, always check whether the feature is documented in the README.md file, though for new Parse Server options this is optional rather than required.

Learnt from: EmpiDev
Repo: parse-community/parse-server PR: 9770
File: spec/CloudCode.spec.js:446-469
Timestamp: 2025-08-26T14:06:31.853Z
Learning: In the Parse Server codebase, when handling query objects in maybeRunAfterFindTrigger, objects without a where property that contain options like limit/skip should be treated as query JSON with an empty where clause using the spread pattern { where: {}, ...query }, not nested as { where: query }.

Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-08T13:46:04.940Z
Learning: For new Parse Server options, verify that the option is documented in src/Options/index.js and that npm run definitions has been executed to reflect changes in src/Options/docs.js and src/Options/Definitions.js. README.md documentation is a bonus but not required for new options.

coderabbitai[bot] avatar Nov 22 '25 12:11 coderabbitai[bot]