GraphQL Security Hardening Checklist | Cosmo by WunderGraph - WunderGraph
A production hardening checklist for your GraphQL router
Disable introspection. Turn off dev mode. Configure CORS allowlists. Enable Redis-backed rate limiting. Restrict operations to pre-approved queries. All in YAML.
Rate limiting requires a Redis instance. TLS requires certificates.
The problem
Default configurations are built for development, not production
Every convenience feature enabled during development becomes an attack surface in production. Most teams do not know which defaults to change before they ship.
Default configurations expose more than production needs
Introspection, file uploads, and open CORS are enabled by default. Development mode is off by default, but worth confirming before you ship. Each is a feature useful during development and an attack surface in production.
No rate limiting means subgraphs absorb any load
Without rate limiting, a burst of requests — automated or not — passes directly to subgraphs. At scale, this causes service degradation or outages for all users.
Open CORS lets any origin trigger cross-site requests
The default CORS configuration allows all origins. In a production deployment that accepts credentials, this enables cross-site request forgery from any page a user visits.
Our solution
Systematic attack surface reduction
The Cosmo Security Hardening Guide provides a configuration checklist for production deployments. Each setting addresses a specific attack vector with a concrete YAML change.
Production hardening checklist
- Disable introspection with
introspection.enabled: falseto hide the API schema from clients. - Set
dev_mode: false. Development mode enables Advanced Request Tracing (ART), which can expose sensitive information, along with pretty log output. - Set
file_upload.enabled: falseif your API does not need multipart file upload support. - Configure CORS with an explicit
allow_originslist, specificallow_methods, and trustedallow_headers. - Enable Redis-backed rate limiting with GCRA algorithm. Configure rate, burst, and period for your traffic profile.
- Consider enabling
block_non_persisted_operationsto limit execution to pre-approved operations only. - Enable TLS and consider Config Signing for a fully hardened production deployment.
Before & After
| Default | Hardened |
|---|---|
| Introspection enabled by default in production | introspection.enabled: false |
| All origins allowed by CORS | Explicit allow_origins and allow_headers list |
| No rate limiting on subgraph traffic | GCRA rate limiting via Redis with configurable rate and burst |
| Any GraphQL operation accepted | block_non_persisted_operations restricts to approved queries |
Per-key overrides for different consumers
Set a global rate, burst, and period. Use key_suffix_expression to derive a key from JWT claims. Define overrides with matching regex patterns to give specific consumer types different limits. The first matching override wins.
Four steps to a hardened router
01 Disable exposure
Set introspection.enabled: false, dev_mode: false, and file_upload.enabled: false. These three settings remove attack surface that production APIs do not need: schema discovery, ART exposure, and multipart upload pressure.
02 Lock down origins
Configure cors.allow_origins with your specific domains, cors.allow_methods to POST and GET only, and cors.allow_headers to the headers your clients use. The default allows all origins. Use explicit allowlists in production.
03 Enable rate limiting
Set rate_limit.enabled: true with a Redis URL. The GCRA (leaky bucket) algorithm uses three parameters: rate (requests per period), burst (allowed burst above rate), and period (time window, e.g. 1s). Test limits before applying to production.
04 Restrict operations
Upload persisted operations via the wgc CLI and set security.block_non_persisted_operations.enabled: true. Only pre-approved operations execute. Also consider block_mutations and block_subscriptions if your API does not use them.
Hardening controls
Every attack vector addressed
Configuration-driven controls with no code changes in subgraphs.
Schema and mode controls
introspection.enabled: false hides the schema. dev_mode: false disables ART and pretty log output. file_upload.enabled: false removes multipart pressure from the router.
CORS allowlists
Specify allow_origins with your trusted domains, allow_methods with POST and GET, and allow_headers with the specific headers your clients send. Open CORS is the default. Use explicit allowlists in production.
GCRA rate limiting
Redis-backed GCRA (leaky bucket) rate limiting. Set rate, burst, and period globally. Use key_suffix_expression to derive per-consumer keys from JWT claims and define per-key overrides for different traffic tiers.
Operation restrictions
Upload pre-approved operations via wgc CLI and enable block_non_persisted_operations. Add block_mutations or block_subscriptions if those operation types are not used by your API.
Harden your router before you ship
Follow the checklist in the hardening guide. Every setting is a YAML change.
FAQ
Security Hardening on Cosmo Router
- Is introspection disabled by default?
- What does development mode expose?
- What rate limiting algorithm does the router use?
- Can I apply different rate limits to different clients?
- What are persisted operations and why do they help?
- What is the recommended log level for production?
Full checklist in the Security Hardening Guide.