GraphQL Query Cost Analysis

GraphQL Query Cost Analysis
Cost Analysis in GraphQL

You must have understood that GraphQL is a very powerful language! Indeed, it is the query language used by the world's largest social network: Facebook (they created it in 2012 and made it public in 2015). However, these benefits and power also come with added complexity. This complexity leads to several vulnerabilities. One of them is failure to Appropriately Rate-limit. In this article, we will focus on this problem.

What is rate limiting?

The principle of rate limiting the API is simple. Indeed, allowing everyone unlimited access to the API allows anyone to use the API at any time and any number of times, potentially preventing other legitimate users from accessing the API.

In short, the API rate limiting restricts people's (and bots') access to the API based on rules/policies set by the API operator or owner.

The increased complexity of GraphQL APIs makes implementing rate-limiting and other denial-of-service protections much more difficult. Whereas with a REST API, each HTTP request performs exactly one action, a GraphQL query can handle many actions and thus take many server resources. Because of this, the same rate-limiting strategies used for REST APIs – to limit the number of HTTP requests received – are generally insufficient to protect a GraphQL API.

Server-side and client-side throughput limits are crucial to maximize reliability and minimize latency. The larger the system/API, the higher the throughput limit API owners typically measure the transaction per second (TPS) limit. On some systems, there may be physical restrictions on data transfer. Both are part of the backend rate limit. To avoid API overhead, API owners often limit the requests, and the amount of data clients can consume. This is called the application rate limit.

With great power comes great responsibilities

As explained above, one of the strengths of GraphQL is that it is very powerful in terms of queries: a GraphQL call can be very complex and sometimes correspond to thousands of REST queries. While a single GraphQL call would fall well below the REST API rate limit, the query might be just as expensive for GitHub's servers to compute.

But as powerful and flexible as GraphQL is, it's important to recognize that this flexibility can lead to several potential security issues. The ability to request any data at any volume can be exploited in the form of Denial of Service (DoS) attacks.

A malevolent user could craft a query that's excessively large or complex, which could consume a substantial amount of resources to process. This could slow down the API server or, in extreme cases, make it unavailable for other users, which is a classic example of a DoS attack. This issue can be exacerbated if nested queries are allowed, as a deeply nested query can multiply data processing requirements exponentially.

In addition, excessive data return is another security issue to consider. If unrestricted, GraphQL queries can potentially expose more data than necessary, which concerns data privacy.

In a real-world example, Yelp's GraphQL API was found to be vulnerable to DoS attacks due to its nested querying capabilities. Cybersecurity researchers were able to construct recursive queries that required excessive computational resources, severely affecting Yelp's API performance. This incident underlines the importance of appropriately limiting GraphQL queries to prevent similar vulnerabilities.

GraphQL vs. REST queries

Here we try to obtain some data that we are interested in. By opting for REST, we have to access several endpoints using several GET requests, while, as we can see in the second diagram, to get the same data using GraphQL, we need a single request that allows us to specify exactly what it does to us and not a whole block of data.

Where the rate limit can come into play is that let's imagine a much more complex request that would require hundreds of GET calls using the REST API, and which would therefore be rejected because it exceeds the authorized limit; with GraphQL, we lose this control since initially we have the impression that it's a fairly simple request due to the syntax of the requested language.

3 requests in REST…
… can be only 1 on GraphQL

Query complexity limits and remediation

GraphQL's synthetic and flexible spirit is one of its greatest strengths. Indeed, as explained above, it is possible to measure the complexity of a query according to its length: short queries can be very expensive without much nesting depth or query length. This issue was addressed very early on in the first tests with the API, and the solution is very simple: we should not treat all GraphQl transactions in the same way: we try to give a precise cost for each transaction according to its complexity

For small - or inexpensive - transactions, we let many through each second and don't charge much for paid API plans. For large - or expensive - transactions, we let fewer through each second and charge API consumers more money per transaction.

This cost analysis-based solution is very promising since we can define a "cost" per field and then analyze the AST to estimate the total cost of the GraphQL query.

Block request with complexity

How GraphQL Armor addresses these issues

Our free and open-source middleware graphql-armor has been built to handle this issue for you!

GraphQL Armor works with Yoga and Apollo applications. The middleware can be configured to set limits on various parameters such as the maximum cost of a query, the maximum number of fields in a query, aliases, depth, characters, and more.

By putting these restrictions in place, GraphQL Armor can effectively block overly complex queries that could potentially lead to DoS attacks or excessive data returns. For instance, by setting a maximum query depth, GraphQL Armor can prevent deeply nested queries that could overload your API server. Similarly, by setting a maximum cost per query, it can restrict excessively demanding operations and ensure the API's resources are distributed fairly among users.

Conclusion

In conclusion, while GraphQL provides powerful capabilities for querying data, developers must be aware of the potential security issues. You can mitigate risks like DoS attacks and excessive data returns through tools like GraphQL Armor and careful design of your GraphQL schema and resolvers. Remember, leveraging its flexibility is key to using GraphQL effectively while ensuring that it's configured with appropriate safeguards.

Want to learn further? 💡

Don't overlook the importance of query cost analysis and rate limiting in GraphQL. To deepen your understanding, here are some helpful resources:

Escape is a proud member of the GraphQL Foundation. Join us at GraphQLConf 2023 on September 19-21 in San Francisco!

GraphQConf 2023