Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/ROOT/content-nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* xref:security/impersonation-and-user-switching.adoc[]
* xref:security/operations.adoc[]
* xref:security/securing-a-graphql-api.adoc[]
* xref:security/avoid-unbounded-queries.adoc[Avoid unbounded queries]
* *Reference*
* xref:neo4jgraphql-class.adoc[]
Expand Down
18 changes: 17 additions & 1 deletion modules/ROOT/pages/neo4jgraphql-class.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export type Neo4jFeaturesSettings = {
attributeFilters?: boolean;
};
vector?: Neo4jVectorSettings;
limitRequired?: boolean;
complexityEstimators?: boolean;
};
----

Expand Down Expand Up @@ -81,4 +83,18 @@ See xref:optimization.adoc#_exclude_deprecated_fields[Exclude `@deprecated` fiel
=== Vector settings

Set your GenAI provider credentials with the `vector` field.
See xref:directives/indexes-and-constraints.adoc#_vector_index_search[`@vector`].
See xref:directives/indexes-and-constraints.adoc#_vector_index_search[`@vector`].


=== Limit required settings

Set `limitRequired` to `true` to require a `limit` argument for all read operations that return lists.
This is useful to prevent unbounded queries that can lead to performance issues.
See xref:security/avoid-unbounded-queries.adoc#_limit_required[Require `limit` argument].


=== Complexity estimators settings

Set `complexityEstimators` to `true` to enable the assignment of a complexity "score" to each field in the schema.
This feature creates complexity estimators compatible with the link:https://www.npmjs.com/package/graphql-query-complexity[graphql-query-complexity] library, which can be used to generate the total complexity score of an incoming query via the `getComplexity` function.
See xref:security/avoid-unbounded-queries.adoc#_complexity_estimators[Complexity estimators].
184 changes: 184 additions & 0 deletions modules/ROOT/pages/security/avoid-unbounded-queries.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
= Avoid unbounded queries
:description: This section describes how to prevent unbounded queries from reaching the server that can lead to performance issues.

Unbounded queries can cause performance issues and potentially crash your application by consuming excessive resources. The Neo4j GraphQL Library provides several mechanisms to help you avoid these problems.

== Require `limit` argument

Setting `limitRequired` to `true` in the feature settings makes the `limit` argument non-nullable in the generated schema, forcing all read operations to include a `limit` argument on all fields that return a list of items.

This protects against queries that return massive datasets across nested relationships.

[source, javascript, indent=0]
----
const neoSchema = new Neo4jGraphQL({
typeDefs,
features: {
limitRequired: true
}
});
----

When this feature is enabled:

* All queries that return arrays must include a `limit` argument
* Queries without a `limit` will not be compliant with the GraphQL schema
* This applies to both top-level queries and nested relationship queries

Assuming the following type definitions:
[source, graphql, indent=0]
----
type Movie @node {
title: String
actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
}

type Actor @node {
name: String
}
----

* Valid query:
[source, graphql, indent=0]
----
query {
movies(limit: 1) {
title
actors(limit: 1) {
name
}
}
}
----

* Invalid query, `limit` argument not provided on `movies` field:
[source, graphql, indent=0]
----
query {
# Error: `Field "movies" argument "limit" of type "Int!" is required, but it was not provided`
movies {
title
actors(limit: 1) {
name
}
}
}
----

* Invalid query, `limit` argument not provided on `actors` field:
[source, graphql, indent=0]
----
query {
movies(limit: 1) {
title
# Error: `Field "actors" argument "limit" of type "Int!" is required, but it was not provided`
actors {
name
}
}
}
----


== Complexity estimators

Setting `complexityEstimators` to `true` enables the assignment of a complexity "score" to each field in your schema. This feature creates complexity estimators that are compatible with the link:https://www.npmjs.com/package/graphql-query-complexity[graphql-query-complexity] library.

You can then use the complexity "score" to automatically reject queries that exceed your defined complexity limits before reaching the server, protecting your system from resource-intensive operations.

[source, javascript, indent=0]
----
const neoSchema = new Neo4jGraphQL({
typeDefs,
features: {
complexityEstimators: true
}
});
----

When this feature is enabled:

* Each field in your schema is assigned a complexity score
* You can use the `getComplexity` function from `graphql-query-complexity` to calculate the total complexity of incoming queries
* You can set maximum complexity thresholds to reject overly complex queries

=== Example usage

To use the complexity estimators, include this logic to run when receiving a query in your GraphQL server implementation.
For example, if you are using Apollo Server, you can use the `requestDidStart` function of an `ApolloServerPlugin`.

[source, javascript, indent=0]
----
const complexity = getComplexity({
schema, // result of Neo4jGraphQL.getSchema()
query, // the GraphQL query document sent to server for execution
estimators: DefaultComplexityEstimators, // exported from the neo4j/graphql package
});

// Define your maximum complexity threshold
const complexityThreshold = 1000;
if (complexity > complexityThreshold) {
throw new Error(`Query is too complex: ${complexity}. Maximum allowed complexity is ${complexityThreshold}.`);
}
----

=== Computing the complexity

The provided complexity estimators compute the complexity of a query using the following formula:
- Each field has a base complexity of 1
- If a field returns a list, the complexity of its children is multiplied by the `limit` argument provided on that field to which the field's complexity is added
- If no `limit` argument is provided, a default multiplier of 1 is used

[NOTE]
====
c_query = c_child * lvl_limit + c_field

where c_field = 1, lvl_limit defaults to 1
====

The queries below demonstrate the usage of the formula.

[source, graphql, indent=0]
----
query {
movies(limit: 10) { # c = 10 * 102 + 1 = 1021
title # c = 1
actors(limit: 50) { # c = 50 * 2 + 1 = 101
name # c = 1
age # c = 1
}
}
}
# Total complexity = 1 + 10 * (1 + 1 + 50 * (1 + 1)) = 1021
----

[source, graphql, indent=0]
----
query {
productionsConnection(first: 10, sort: [{ title: ASC }]) { # (12 + 2x) * 10 + 1 = 121 + 20x
edges { # 12 + 2x
node { # 9 + 2x + 1 + 1 = 11 + 2x
title # 1
actorsConnection(first: 2, sort: { node: { name: ASC } }) { # (4 + x) * 2 + 1 = 9 + 2x
edges { # 3 + x + 1 = 4 + x
node { # 2
name # 1
}
properties { # 1 + x; x = max nr of properties in below fragments
... on ActedIn {
roles
}
... on ActedInSeries {
roles
episodes
year
}
}
}
}
}
}
}
}
# Total complexity = 1 + 10 * (1 + 1 + 1 + 1 + 2 * (1 + 1 + 1 + 1 + 3)) = 181
----
3 changes: 2 additions & 1 deletion modules/ROOT/pages/security/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ auth/authorization/where.adoc, authentication-and-authorization/index.adoc
* xref::/security/subscriptions-authorization.adoc[Subscriptions authorization] - Rule-based authorization for subscriptions with the `@subscriptionsAuthorization` directive.
* xref::/security/impersonation-and-user-switching.adoc[Impersonation and user switching] - How to set up impersonation and user switching features.
* xref::/security/operations.adoc[Operation examples] - GraphQL query examples on how to trigger the evaluation of different authentication and authorization rules.
* xref::/security/securing-a-graphql-api.adoc[Securing a GraphQL API] - A tutorial to improve the security of a GraphQL API.
* xref::/security/securing-a-graphql-api.adoc[Securing a GraphQL API] - A tutorial to improve the security of a GraphQL API.
* xref::/security/avoid-unbounded-queries.adoc[Avoid unbounded queries] - Strategies to avoid unbounded queries reaching the server through mandatory limit arguments or evaluating the complexity of incoming queries.