Join us from October 8-10 in New York City to learn the latest tips, trends, and news about GraphQL Federation and API platform engineering.Join us for GraphQL Summit 2024 in NYC
Docs
Start for Free

Enforcing Operation Limits in the GraphOS Router

Set constraints on depth, height, aliases, and root fields


Want to learn about graph security in-person?

Don't miss the Securing your graph: A defense-in-depth strategy workshop at this year's GraphQL Summit.

This feature is only available with a GraphOS Enterprise plan.
You can test it out by signing up for a free Enterprise trial.

You can define operation limits in your 's configuration to reject potentially malicious requests. An that exceeds any specified limit is rejected (unless you run your router in warn_only mode).

Setup

To use operation limits, you must run v1.17 or later of the . Download the latest version.

You define operation limits in your router's YAML config file, like so:

router.yaml
limits:
max_depth: 100
max_height: 200
max_aliases: 30
max_root_fields: 20
# Uncomment to enable warn_only mode
# warn_only: true

Each limit takes an integer value. You can define any combination of supported limits.

Supported limits

max_depth

Limits the deepest nesting of in an operation, including in .

The GetBook operation below has depth three:

query GetBook {
book { # Depth 1 (root field)
...bookDetails
}
}
fragment bookDetails on Book {
details { # Depth 2 (nested under `book`)
... on ProductDetailsBook {
country # Depth 3 (nested under `details`)
}
}
}

max_height

Limits the number of unique fields included in an operation, including fields of fragments. If a particular field is included multiple times via , it's counted only once.

The GetUser operation below has height three:

query GetUser {
user { # 1
id # 2
name # 3
username: name # Aliased duplicate (not counted)
}
}

Each unique increments an operation's height by one, regardless of that field's return type (, object, or list).

max_aliases

Limits the total number of fields in an operation, including fields of fragments.

The GetUser operation below includes three aliases:

query GetUser {
user {
nickname: name # 1
username: name # 2
handle: name # 3
}
}

Each aliased field increments the alias count by one, regardless of that field's return type (scalar, object, or list).

max_root_fields

Limits the number of root fields in an operation, including root fields in fragments. If a particular root field is included multiple times via aliases, each usage is counted.

The following operation includes three root fields:

query GetTopProducts {
topBooks { # 1
id
}
topMovies { # 2
id
}
topGames { # 3
id
}
}

warn_only mode

If you run your router in warn_only mode, that exceed defined limits are not rejected. Instead, the router processes these operations as usual and emits a WARN trace that notes all exceeded limits, like so:

2023-03-15T19:08:23.123456Z WARN apollo_router::operation_limits: max_depth exceeded, max_depth: 3, current_op_depth: 5, operation: "query GetOwnerLocation {cat {owner {location {postalCode}}}}"

Running in warn_only mode can be useful while you're testing to determine the most appropriate limits to set for your .

You can enable or disable warn_only mode in your router's YAML config file, like so:

router.yaml
limits:
warn_only: true # warn_only mode always enabled

Response format for exceeded limits

Whenever your router rejects a request because it exceeds an operation limit, the router responds with a 400 HTTP status code and a standard error response body:

# HTTP 400
{
"data": {},
"errors": [
{
"message": "Maximum height (field count) limit exceeded in this operation",
"extensions": {
"code": "MAX_HEIGHT_LIMIT"
}
}
]
}

If you run your router in warn_only mode, the router logs the limit violation but executes the operation as normal, returning a 200 status code with the expected response.

Using telemetry to set operation limits

telemetry can help you set operation limits, especially when you have a large number of existing operations. You can measure incoming operations over a fixed duration, then use the captured data as a baseline configuration.

Logging values

To log limit information about every operation, you can configure the router with a custom event to log the values of aliases, depth, height, and root_fields for each operation:

router.yaml
telemetry:
instrumentation:
events:
supergraph:
OPERATION_LIMIT_INFO:
message: operation limit info
on: response
level: info
attributes:
graphql.operation.name: true
query.aliases:
query: aliases
query.depth:
query: depth
query.height:
query: height
query.root_fields:
query: root_fields

NOTE

For a large amount of traffic, you may prefer to collect and export metrics to your APM instead.

Collecting metrics

To capture and view metrics to help set your operation limits, you can configure the router to collect custom metrics on the values of aliases, depth, height, and root_fields for each operation:

router.yaml
telemetry:
exporters:
metrics:
common:
views:
# Define a custom view because operation limits are different than the default latency-oriented view of OpenTelemetry
- name: oplimits.*
aggregation:
histogram:
buckets:
- 0
- 5
- 10
- 25
- 50
- 100
- 500
- 1000
instrumentation:
instruments:
supergraph:
oplimits.aliases:
value:
query: aliases
type: histogram
unit: number
description: "Aliases for an operation"
oplimits.depth:
value:
query: depth
type: histogram
unit: number
description: "Depth for an operation"
oplimits.height:
value:
query: height
type: histogram
unit: number
description: "Height for an operation"
oplimits.root_fields:
value:
query: root_fields
type: histogram
unit: number
description: "Root fields for an operation"

You should also configure the router to export metrics to your APM tool.

Previous
Subgraph Authentication
Next
Safelisting with Persisted Queries
Rate articleRateEdit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc., d/b/a Apollo GraphQL.

Privacy Policy

Company