Secure your GraphQL API with Zuplo
GraphQL gives clients enormous flexibility over what they ask for — which is also what makes it easy to abuse. A single request can nest arbitrarily deep, fan out into thousands of resolver calls, or map your entire schema through introspection. Zuplo ships three policies that close these gaps at the edge, before a malicious request ever reaches your origin:
| Risk | Policy |
|---|---|
| Deep / costly query | graphql-complexity-limit-inbound |
| Schema discovery | graphql-disable-introspection-inbound |
| Schema over-sharing | graphql-introspection-filter-outbound |
This guide explains each risk and shows the policy configuration that addresses it. It assumes you've already added a GraphQL endpoint to your gateway.
Understand the risks
Deeply nested or expensive queries
Without limits, a client can send a deeply nested query that forces your server to resolve a huge graph of data, or a flat-but-expensive query that triggers thousands of resolver calls. Either can exhaust resources and cause a denial-of-service — whether the client is a deliberate attacker or simply unaware of the cost of their query.
Introspection
GraphQL lets clients introspect your schema with __schema and __type
queries. This is invaluable during development, but in production it hands
potential attackers a complete map of your types, fields, and relationships.
Limit query depth and complexity
The
graphql-complexity-limit-inbound
policy guards against expensive queries in two complementary ways. You can use
either or both.
Depth limit
A depth limit caps how many levels a query can nest. It needs no schema and is
the simplest first line of defense. Add it under useDepthLimit:
config/policies.json
Complexity limit
A complexity limit scores each query against your schema and rejects queries above a threshold, catching expensive queries that aren't necessarily deep. Because it scores against the schema, it needs your GraphQL endpoint URL for introspection. Combine it with the depth limit:
config/policies.json
A query that exceeds either limit is rejected with a 400 and a GraphQL error;
requests within the limits pass through. See the
policy reference for the
full option set.
Disable introspection in production
The
graphql-disable-introspection-inbound
policy blocks any query containing __schema or __type with a 403, hiding
your schema from clients. It takes no options:
config/policies.json
Expose a partial schema instead
Disabling introspection entirely isn't always what you want. If you need clients
(or MCP-connected AI agents) to introspect part of
your schema while keeping sensitive types and fields hidden, use the outbound
graphql-introspection-filter-outbound
policy. It strips chosen types and fields from introspection responses:
config/policies.json
Attach the policies to your route
Add the inbound policies to your GraphQL route in routes.oas.json. The
outbound filter policy goes in the outbound array:
config/routes.oas.json
Example repository
For a complete, runnable setup, see the GraphQL API with Zuplo example repository.
Next steps
- Cache GraphQL responses — cut latency with edge caching
- Test GraphQL queries — verify your secured endpoint
- GraphQL analytics — surface errors and monitor traffic