Skip to content

GraphQL Limits Compliance

You can configure the API Firewall to validate incoming GraphQL queries against predefined query constraints. By adhering to these limits, you can shield your GraphQL API from malicious queries, including potential DoS attacks. This guide explains how the firewall calculates query attributes like node requests, query depth, and complexity before aligning them with your set parameters.

When running the API Firewall Docker container for a GraphQL API, you set limits using the following environment variables:

Environment variable Description
APIFW_GRAPHQL_MAX_QUERY_COMPLEXITY Defines the maximum number of Node requests that might be needed to execute the query. Setting it to 0 disables the complexity check.
APIFW_GRAPHQL_MAX_QUERY_DEPTH Specifies the maximum permitted depth of a GraphQL query. A value of 0 means the query depth check is skipped.
APIFW_GRAPHQL_NODE_COUNT_LIMIT Sets the upper limit for the node count in a query. When set to 0, the node count limit check is skipped.
APIFW_GRAPHQL_MAX_ALIASES_NUM Sets a limit on the number of aliases that can be used in a GraphQL document. If this variable is set to 0, it implies that there is no limit on the number of aliases that can be used.
APIFW_GRAPHQL_FIELD_DUPLICATION Defines whether to allow or prevent the duplication of fields in a GraphQL document. The default value is false (prevent).
APIFW_GRAPHQL_BATCH_QUERY_LIMIT Sets a limit on the number of queries that can be batched together in a single GraphQL request. If this variable is set to 0, it implies that there is no limit on the number of batched queries.

How limit calculation works

API Firewall leverages the wundergraph/graphql-go-tools library, which adopts algorithms similar to those used by GitHub for calculating GraphQL query complexity. Central to this is the OperationComplexityEstimator function, which processes a schema definition and a query, iteratively examining the query to get both its complexity and depth.

You can fine-tune this calculation by integrating integer arguments on fields that signify the number of Nodes a field returns:

  • directive @nodeCountMultiply on ARGUMENT_DEFINITION

    Indicates that the Int value the directive is applied on should be used as a Node multiplier.

  • directive @nodeCountSkip on FIELD
    Indicates that the algorithm should skip this Node. This is useful to whitelist certain query paths, e.g. for introspection.

For documents with multiple queries, the calculated complexity, depth, and node count apply to the whole document, not just the single query being run.

Calculation examples

Below there are a few examples which will grant a clearer perspective on the calculations. They are based on the following GraphQL schema:

type User {
    name: String!
    messages(first: Int! @nodeCountMultiply): [Message]
}

type Message {
    id: ID!
    text: String!
    createdBy: String!
    createdAt: Time!
}

type Query {
        __schema: __Schema! @nodeCountSkip
    users(first: Int! @nodeCountMultiply): [User]
    messages(first: Int! @nodeCountMultiply): [Message]
}

type Mutation {
    post(text: String!, username: String!, roomName: String!): Message!
}

type Subscription {
    messageAdded(roomName: String!): Message!
}

scalar Time

directive @nodeCountMultiply on ARGUMENT_DEFINITION
directive @nodeCountSkip on FIELD

The depth always represents the nesting levels of fields. For instance, the query below has a depth of 3:

{
    a {
        b {
            c
        }
    }
}

Example 1

query {
  users(first: 10) {
    name
    messages(first:100) {
      id
      text
    }
  }
}
  • NodeCount = {int} 1010

    Node count = 10 [users(first: 10)] + 10*100 [messages(first:100)] = 1010
    
  • Complexity = {int} 11

    Complexity = 1 [users(first: 10)] + 10 [messages(first:100)] = 11
    
  • Depth = {int} 3

Example 2

query {
  users(first: 10) {
    name
  }
}
  • NodeCount = {int} 10

    Node count = 10 [users(first: 10)] = 10
    
  • Complexity = {int} 1

    Complexity = 1 [users(first: 10)] = 1
    
  • Depth = {int} 2

Example 3

query {
  message(id:1) {
    id
    text
  }
}
  • NodeCount = {int} 1

    Node count = 1 [message(fid:1)] = 1
    
  • Complexity = {int} 1

    Complexity = 1 [messages(first:1)] = 1
    
  • Depth = {int} 2

Example 4

query {
  users(first: 10) {
    name
    messages(first:1) {
      id
      text
    }
  }
}
  • NodeCount = {int} 20

    Node count = 10 [users(first: 10)] + 10*1 [messages(first:1)] = 20
    
  • Complexity = {int} 11

    Complexity = 1 [users(first: 10)] + 10 [messages(first:1)] = 11
    
  • Depth = {int} 3

Example 5 (introspection query)

query IntrospectionQuery {
  __schema {
    queryType {
      name
    }
    mutationType {
      name
    }
    subscriptionType {
      name
    }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}

fragment InputValue on __InputValue {
  name
  description
  type {
    ...TypeRef
  }
  defaultValue
}

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}
  • NodeCount = {int} 0

  • Complexity = {int} 0

  • Depth = {int} 0

Since the __schema: __Schema! @nodeCountSkip directive is present in the schema, the calculated NodeCount, Complexity, and Depth are all 0.

Example 6 (limiting batched queries)

Assume you have set the APIFW_GRAPHQL_BATCH_QUERY_LIMIT environment variable to 2. If you attempt to execute the following batch of 3 GraphQL queries sequentially against the backend:

[
  {"query":"query {\n  systemHealth\n}","variables":[]},
  {"query":"query {\n  systemHealth\n}","variables":[]},
  {"query":"query {\n  systemHealth\n}","variables":[]}
]

The API Firewall will intercept this request and log an error, indicating that the number of queries in the batch exceeds the configured limit. The logged error message will be:

ERROR GraphQL query validation error=the batch query limit has been exceeded. The number of queries in the batch is 3. The current batch query limit is 2 protocol=HTTP