{"id":20253633,"url":"https://github.com/graphile/depth-limit","last_synced_at":"2025-04-10T23:43:32.090Z","repository":{"id":234224516,"uuid":"787055278","full_name":"graphile/depth-limit","owner":"graphile","description":"A GraphQL.js validation rule for limiting the depth (including list depth) of GraphQL operations","archived":false,"fork":false,"pushed_at":"2024-06-17T10:15:05.000Z","size":144,"stargazers_count":23,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T23:43:28.469Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/graphile.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-15T20:00:07.000Z","updated_at":"2025-04-10T00:54:36.000Z","dependencies_parsed_at":"2024-04-18T15:16:44.965Z","dependency_job_id":"9302d6d3-b120-483a-9e5c-97b0ff04368f","html_url":"https://github.com/graphile/depth-limit","commit_stats":null,"previous_names":["graphile/depth-limit"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphile%2Fdepth-limit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphile%2Fdepth-limit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphile%2Fdepth-limit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphile%2Fdepth-limit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/graphile","download_url":"https://codeload.github.com/graphile/depth-limit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248317726,"owners_count":21083527,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T10:26:27.890Z","updated_at":"2025-04-10T23:43:32.068Z","avatar_url":"https://github.com/graphile.png","language":"JavaScript","funding_links":["https://github.com/sponsors/benjie"],"categories":[],"sub_categories":[],"readme":"# @graphile/depth-limit\n\nA highly capable depth limiting\n[GraphQL.js](https://github.com/graphql/graphql-js) validation rule to help\nprotect your server from malicious operations.\n\n- Limits depth of field selections in an operation\n- Limits number of times a list may be nested in an operation\n- Limits number of times the same field coordinate may be (directly or\n  indirectly) nested\n- Has separate limits for introspection queries versus regular operations\n- Can add limits for specific fields to say how many times they may be nested\n  (e.g. allow \"friends of friends\" but deny \"friends of friends of friends\" via\n  `{'User.friends': 2}`)\n- Can be configured such that each fragment reference also increments the depth\n  (not recommended)\n\n## FAQ\n\n### Can I trust this module?\n\nYou're right to check! This module is maintained by\n[Benjie Gillam](https://github.com/benjie), a member of the\n[GraphQL Technical Steering Committee](https://github.com/graphql/graphql-wg/blob/main/GraphQL-TSC.md#tsc-members).\n\nThe module has no dependencies (though it does have a \"peer\" dependency of\n`graphql`, of course!), so auditing should be straightforward.\n\nThe module is MIT licensed, so as it says in the license: THE SOFTWARE IS\nPROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\nBUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS\nBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\nCONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n### Is this module Graphile-specific?\n\nNo! This rule should work for any server that allows customizing GraphQL.js\nvalidation rules. It's not specific to PostGraphile, Gra*fast*, Grafserv or any\nother Graphile software.\n\n### Do I need this module?\n\nGraphQL is a very powerful technology, but it must be handled carefully - it's\nrelatively easy for a bad actor to write a small GraphQL query that causes the\nserver to perform a disporportionaly large amount of work. For this reason, it's\nhighly recommended that you only accept\n[trusted documents](https://benjie.dev/graphql/trusted-documents) to your\nGraphQL server - like Facebook do internally.\n\nIf your GraphQL API is intended to be consumed by untrusted (or not fully\ntrusted) third parties then you cannot rely on the security benefits of the\nTrusted Documents pattern, and thus you need to protect yourself from malicious\nqueries. This rule is one very easy to use piece of protection (though you\nshould note that it is not sufficient on its own, you should still use all the\ncommon patterns of protecting your API: rate limiting, circuit breakers,\nexecution timeouts, enforcing pagination limits, body size limits, etc etc).\n\n### Do I need this if I am using Trusted Documents (aka persisted queries)?\n\nEven if you are using the\n[Trusted Documents](https://benjie.dev/graphql/trusted-documents) (persisted\nqueries, stored operations, etc) pattern (which you should be if your API is\nonly intended to be consumed by your own websites, mobile apps and other\nfirst-party software), this is still a useful validation rule.\n\nThough Trusted Documents means that the concern of malicious operations is moot\n(every document is trusted, so none are expected to be malicious), this rule\nstill helps to prevent your engineers from writing queries that are too complex\nand could potentially be too costly for your server. It's reasonable, when using\nTrusted Documents, to increase the limits as needed.\n\n## Usage\n\nInstall the module (and `graphql`!) with your package manager of choice:\n\n```\nyarn add graphql @graphile/depth-limit\n```\n\nHow you use it then depends on which server software you're using.\n\n### graphql-http\n\nAdd `depthLimit()` to the `validationRules` list passed to the `createHandler`\ncall. Here's an example based on the \"With `http`\" usage example\n[in the graphql-http docs](https://github.com/graphql/graphql-http/tree/9985b4fbb5b2316b3f6987e67f560ef1ce91a7b9?tab=readme-ov-file#with-http):\n\n```ts\nimport http from \"http\";\nimport { createHandler } from \"graphql-http/lib/use/http\";\nimport { depthLimit } from \"@graphile/depth-limit\";\nimport { schema } from \"./schema.js\";\n\n// Create the GraphQL over HTTP handler\nexport const handler = createHandler({\n  schema,\n  validationRules: [\n    depthLimit({\n      // Options here\n    }),\n  ],\n});\n```\n\n### Envelop\n\nIf you're using the excellent\n[envelop plugin system](https://the-guild.dev/graphql/envelop) for GraphQL you\ncan use our handy `useDepthLimit` export to build an envelop plugin to add the\nvalidation rule:\n\n```ts\nimport * as GraphQLJS from \"graphql\";\nimport { envelop, useEngine } from \"@envelop/core\";\nimport { useDepthLimit } from \"@graphile/depth-limit\";\n\nconst getEnveloped = envelop({\n  plugins: [\n    useEngine(GraphQLJS),\n    useDepthLimit({\n      // Options here\n    }),\n  ],\n});\n```\n\n### Yoga\n\nWith GraphQL Yoga, you'll follow a similar setup as with Envelop above; see the\ndocumentation on\n[Yoga plugins](https://the-guild.dev/graphql/yoga-server/docs/features/envelop-plugins).\n\n```ts\nimport { createYoga } from \"graphql-yoga\";\nimport { useDepthLimit } from \"@graphile/depth-limit\";\n\nconst yoga = createYoga({\n  plugins: [\n    useDepthLimit({\n      // Options here\n    }),\n  ],\n});\n```\n\n### Raw GraphQL.js\n\nIf you're using the `graphql` module from npm directly, import the `depthLimit`\nfactory function and use its result alongside the `specifiedRules` (the rules\nfrom the GraphQL specification) when validating a GraphQL operation:\n\n```ts\nimport { depthLimit } from \"@graphile/depth-limit\";\nimport { validate, specifiedRules, parse, execute } from \"graphql\";\n\nconst allValidationRules = [\n  // ❗ Don't forget to use the built in GraphQL validation rules!\n  ...specifiedRules,\n\n  //👇👇👇👇\n  depthLimit({\n    // Options here\n  }),\n  //👆👆👆👆\n];\n\n// This is just an example (and assumes the schema has already been validated).\n// The `validate()` call is the part that would differ from what you would\n// normally have; in particular the optional third argument is specified.\nexport function executeGraphQLRequest(\n  schema: GraphQLSchema,\n  source: string,\n  variableValues?: Record\u003cstring, any\u003e,\n) {\n  let document;\n  try {\n    document = parse(source);\n  } catch (syntaxError) {\n    return {\n      errors: [syntaxError],\n    };\n  }\n\n  //                                        👇👇👇👇👇👇👇👇👇\n  const errors = validate(schema, document, allValidationRules);\n  //                                        👆👆👆👆👆👆👆👆👆\n\n  if (errors.length \u003e 0) {\n    return { errors };\n  }\n\n  return execute({ schema, document, variableValues });\n}\n```\n\n## Options\n\nNo matter how you load this validation rule, it accepts the same options:\n\n````ts\nexport type Options = {\n  /**\n   * How many selection sets deep may the user query?\n   *\n   * @defaultValue `12`\n   */\n  maxDepth?: number;\n\n  /**\n   * How many nested lists deep may the user query?\n   *\n   * @defaultValue `4`\n   */\n  maxListDepth?: number;\n\n  /**\n   * How many times may a field reference itself recursively by default. This\n   * is to try and block the most common forms of attack automatically, if you\n   * have legitimate cases where a field should be referenced recursively then\n   * you may specifically override those via `maxDepthByFieldCoordinates`.\n   *\n   * @defaultValue `2`\n   */\n  maxSelfReferentialDepth?: number;\n\n  /**\n   * How many selection sets deep may the user query in introspection?\n   *\n   * @defaultValue `12`\n   */\n  maxIntrospectionDepth?: number;\n\n  /**\n   * How many nested lists deep may the user query in introspection?\n   *\n   * @defaultValue `3`\n   */\n  maxIntrospectionListDepth?: number;\n\n  /**\n   * How many times may an introspection field reference itself recursively by\n   * default. This is to try and block the most common forms of attack\n   * automatically, if you have legitimate cases where a field should be\n   * referenced recursively then you may specifically override those via\n   * `maxDepthByFieldCoordinates`.\n   *\n   * @defaultValue `2`\n   */\n  maxIntrospectionSelfReferentialDepth?: number;\n\n  /**\n   * Set `true` if you want fragment spreads to add to the depth. Not\n   * recommended; fragments are essential to using GraphQL correctly so using\n   * them should not have a penalty.\n   *\n   * Whether this setting is true or false, the fields referenced by the\n   * fragment will still of course weigh into the depth calculations.\n   *\n   * @defaultValue `false`\n   */\n  fragmentsAddToDepth?: boolean;\n\n  /**\n   * Limits the number of times a particular field coordinate can be nested\n   * inside itself; for example:\n   *\n   * ```\n   * maxDepthByFieldCoordinates: {\n   *   'User.friends': 2,\n   * }\n   * ```\n   *\n   * Would allow you to load a users friends, or their friends of friends, but\n   * not their friends of friends of friends.\n   */\n  maxDepthByFieldCoordinates?: Record\u003cstring, number\u003e;\n\n  /**\n   * If true, informs the user what the issues are. Setting this to true could\n   * have security ramifications as it will make it easier for an attacker to\n   * determine your limits (however, this could be derived with minimal effort\n   * also, so is potentially not a big deal).\n   *\n   * @defaultValue `false`\n   */\n  revealDetails?: boolean;\n};\n````\n\n### How to choose the right options for you\n\nFirstly, we've set the introspection defaults to allow the most common\nintrospection queries to pass validation, so you shouldn't need to customize\nthose unless you're doing something fancy.\n\nThe main options you should look at customizing are `maxListDepth`, `maxDepth`\nand `maxSelfReferentialDepth` (in that order).\n\nIf you're starting from scratch you should set your settings low, and work your\nway up as you need to. If you have an existing server then it may make sense to\ntrack your queries for a while and figure out the higest values used (see the\n`countOperationDepths` function below), and set your limits to that to prevent\nmore complex queries.\n\nUltimately, tuning these parameters is more of an art than a science, and this\nis one reason why this validation rule isn't a built in feature of the `graphql`\nmodule or, indeed, the specification.\n\n#### maxListDepth\n\n`maxListDepth` you should set to the lowest value you can tolerate; something\nlike 2, 3 or 4. It's unlikely that your users are successfully paginating\nthrough collections that are deeper than this. Note: `maxListDepth` only counts\nselection sets inside of lists, so scalar lists are ignored and do not add to\nthe count, this is by design.\n\n#### maxDepth\n\n`maxDepth` should also be set to a suitably low value, although it's much more\nacceptable to grow this one. One of the common introspection queries requires a\ndepth of 12 (primarily due to `ofType { ofType { ofType { ... } } }` for\ndetermining the type of a field or argument), so this is the default we use even\nfor non-introspection queries; lower would be better, but higher is also fine if\nyou need it. Note that if your schema uses the Relay Cursor Connection pattern\nit may end up requiring deeper limits than you might realise.\n\n#### maxSelfReferentialDepth\n\n`maxSelfReferentialDepth` defaults to just `2` and prevents attackers from\nexploiting cycles in your graph (e.g.\n`{ friends { friends { friends { ... } } } }`). We recommend that you keep this\nlimit low, and then add specific overrides via `maxDepthByFieldCoordinates` if a\nparticular field is expected to commonly be used in a nested fashion.\n\n#### maxDepthByFieldCoordinates\n\nIf you have a particular cycle in your schema (for example you might have\n`User.friends` which returns `[User]`, and thus you could query it as\n`{ currentUser { friends { friends { friends { friends { ... } } } } } }`) and\nyou would like to prevent people nesting it too many times (or, alternatively,\nwould like to allow them to nest it more than `maxSelfReferentialDepth` times)\nthen you can set specific limits on the number of times a particular field may\nbe referenced recursively by specifying a numeric limit for its\n[schema coordinate](https://github.com/graphql/graphql-wg/blob/main/rfcs/SchemaCoordinates.md#typeattribute),\nfor example:\n\n```ts\nimport { depthLimit } from \"@graphile/depth-limit\";\n\nexport const rule = depthLimit({\n  maxSelfReferentialDepth: 2,\n  maxDepthByFieldCoordinates: {\n    // Allow `{ currentUser { friends { friends { friends { name }  } } } }`\n    // But forbid `{ currentUser { friends { friends { friends { friends { name } } } } } }`\n    \"User.friends\": 3,\n  },\n});\n```\n\n## Community-funded open-source software\n\nI love GraphQL (hence the name: Graphile!) and I am a firm believer in the\nbenefits of open source software; but I don't have time to write awesome\nsoftware like this _and_ go out and earn a days wages. If you use this\nvalidation rule, please consider\n[sponsoring me](https://github.com/sponsors/benjie)\\* so I can keep maintaining\nand building this kind of critical software, and maybe I'll even get a chance to\n[work through my mountain of GraphQL spec PRs](https://github.com/graphql/graphql-spec/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc+author%3Abenjie).\nThanks for considering sponsoring me, there's no way this project would exist\nwithout the support of my sponsors.\n\n\\* _Other sponsorship methods are available, if you're interested drop me an\nemail on my GitHub handle at graphile.com._\n\n## `countOperationDepths`\n\nThis function returns the depths seen in a given document to help you to figure\nout what good values to set for your options are. Most options are ignored here,\nthe main (only?) one that impacts the result of `depths` is\n`fragmentsAddToDepth`.\n\nHere you can provide both the schema SDL (or schema object) and the document\nsource text. If you have already parsed the document then you may wish to use\n`countDepths` instead as it is more performant.\n\n```ts\nimport { countOperationDepths } from \"@graphile/depth-limit\";\n\nconst schema = /* GraphQL */ `\n  type Query {\n    currentUser: User\n  }\n  type User {\n    name: String\n    friends: [User!]!\n  }\n  type Mutation {\n    addFriend(id: ID!): AddFriendPayload\n  }\n  type AddFriendPayload {\n    query: Query\n  }\n`;\n\nconst source = /* GraphQL */ `\n  query FriendsOfFriends {\n    ...FoF\n  }\n  mutation AddFriend($id: ID!) {\n    addFriend(id: $id) {\n      query {\n        ...FoF\n      }\n    }\n  }\n  fragment FoF on Query {\n    currentUser {\n      name\n      friends {\n        name\n        friends {\n          name\n        }\n      }\n    }\n  }\n`;\n\nconst depthsByOperationName = countOperationDepths(schema, source);\n\nimport assert from \"assert\";\nassert.deepEqual(depthsByOperationName, {\n  FriendsOfFriends: {\n    $$depth: 3,\n    $$listDepth: 2,\n    \"Query.currentUser\": 1,\n    \"User.name\": 1,\n    \"User.friends\": 2,\n  },\n  AddFriend: {\n    $$depth: 5,\n    $$listDepth: 2,\n    \"Mutation.addFriend\": 1,\n    \"AddFriendPayload.query\": 1,\n    \"Query.currentUser\": 1,\n    \"User.name\": 1,\n    \"User.friends\": 2,\n  },\n});\n```\n\n## `countDepth`\n\nThis function returns the depths seen in a given operation to help you to figure\nout what good values to set for your options are. Most options are ignored here,\nthe main (only?) one that impacts the result of `depths` is\n`fragmentsAddToDepth`.\n\n```ts\nimport assert from \"assert\";\nimport { countDepth } from \"@graphile/depth-limit\";\nimport { parse, Kind, getOperationAST } from \"graphql\";\nimport { schema } from \"./schema.js\";\n\nconst query = /* GraphQL */ `\n  query FriendsOfFriends {\n    currentUser {\n      name\n      friends {\n        name\n        friends {\n          name\n        }\n      }\n    }\n  }\n`;\n\nconst document = parse(query);\nconst operationName = undefined;\nconst fragments = document.definitions.filter(\n  /** @type {(d: any) =\u003e d is import('graphql').FragmentDefinitionNode} */\n  (d) =\u003e d.kind === Kind.FRAGMENT_DEFINITION,\n);\nconst operation = getOperationAST(document, operationName);\nconst { depths, resolvedOptions } = countDepth(schema, operation, fragments, {\n  // Options here\n});\nassert.deepEqual(depths, {\n  $$depth: 3,\n  $$listDepth: 2,\n  \"Query.currentUser\": 1,\n  \"User.name\": 1,\n  \"User.friends\": 2,\n});\n```\n\n## Caveats\n\nWe count fields on their current named type whilst traversing the document; this\ntype could be an object, interface, or union type. Therefore, a field that\nexists on an interface would be counted as a different coordinate depending on\nif the user accesses it via the interface directly or one of the\nimplementations.\n\n## See also\n\nThis isn't the only depth limiting validation rule, others to check out are:\n\n- [`graphql-depth-limit`](https://www.npmjs.com/package/graphql-depth-limit) -\n  the OG depth limiter, last published 7 years ago and the repository no longer\n  exists\n- [`@escape.tech/graphql-armor-max-depth`](https://github.com/Escape-Technologies/graphql-armor/tree/main/packages/plugins/max-depth) -\n  a depth limiting plugin for the GraphQL Armor system\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphile%2Fdepth-limit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgraphile%2Fdepth-limit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphile%2Fdepth-limit/lists"}