CVSS 6.5 Medium — The GraphQL API served by kubernetes-graphql-gateway is vulnerable to Denial-of-Service (DoS) attacks due to a complete absence of query resource controls (depth limiting, complexity analysis, response size capping, and rate limiting). An authenticated attacker can craft queries that force the server to compute and serialize multi-megabyte responses, consuming significant CPU, memory, and network bandwidth. Repeated requests can exhaust server resources and degrade or deny service to legitimate users.
Note: A previous version of this advisory (based on pre-v1 code) documented an unauthenticated attack surface via an HTTP GET method bypass in the former
registry.go. That bypass has been removed in v1 — all requests now require a Bearer token. The CVSS score has been adjusted from 7.5 to 6.5 accordingly (Privileges Required: None → Low). CWE-306 (Missing Authentication for Critical Function) no longer applies.
The kubernetes-graphql-gateway uses the graphql-go/graphql library (v0.8.1) with the graphql-go/handler HTTP handler. The handler is instantiated in gateway/gateway/graphql/graphql.go with only cosmetic configuration — no resource limits:
// gateway/gateway/graphql/graphql.go — CreateHandler()
func (s *GraphQLServer) CreateHandler(schema *graphql.Schema) *GraphQLHandler {
graphqlHandler := handler.New(&handler.Config{
Schema: schema,
Pretty: s.config.Pretty,
Playground: s.config.Playground,
GraphiQL: s.config.GraphiQL,
})
return &GraphQLHandler{
Schema: schema,
Handler: graphqlHandler,
}
}
The handler.Config struct does not include MaxDepth, MaxComplexity, MaxResponseSize, or any equivalent fields. Neither the graphql-go/handler nor the underlying graphql-go/graphql library provides built-in query depth or complexity analysis.
The application configuration (gateway/gateway/config/config.go) has no fields for resource limits:
// gateway/gateway/config/config.go — GraphQL config
type GraphQL struct {
Pretty bool
Playground bool
GraphiQL bool
}
No rate limiting, throttling, or request size controls exist anywhere in the codebase.
All requests pass through the HTTP handler in gateway/http/http.go, which extracts a Bearer token and injects it into the request context:
// gateway/http/http.go — Token extraction (applied to all methods)
s.Handle("/api/clusters/{clusterName}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clusterName := r.PathValue("clusterName")
authHeader := r.Header.Get("Authorization")
token := strings.TrimPrefix(authHeader, "Bearer ")
ctx := utilscontext.SetToken(r.Context(), token)
ctx = utilscontext.SetCluster(ctx, clusterName)
c.Gateway.ServeHTTP(w, r.WithContext(ctx))
}))
The token is enforced at the Kubernetes API layer via gateway/gateway/roundtripper/bearer.go, which returns HTTP 401 for requests without a valid token. However, the GraphQL execution engine (query parsing, schema validation, introspection) still runs before the Kubernetes API is contacted — meaning authenticated users can trigger expensive operations that consume server resources without hitting the K8s API at all.
The GraphQL schema contains 3,508 types (Kubernetes resources + platform CRDs). Introspection meta-fields (__schema, __type) allow recursive field expansion. Each additional nesting level multiplies the response size exponentially. A single full introspection query generates ~5.2 MB of response data in ~1.15s.
Without rate limiting, an authenticated attacker can issue many concurrent expensive queries. 5 parallel requests generate ~18.6 MB total response in under 4 seconds with no throttling. At scale (e.g. 999 concurrent requests), the backend becomes unresponsive and returns 503 to all users.
The HandleSubscription() method in gateway/gateway/graphql/graphql.go processes SSE (Server-Sent Events) subscription requests. A malicious authenticated client can open many subscription channels simultaneously, holding server connections and memory indefinitely:
// gateway/gateway/graphql/graphql.go — HandleSubscription()
subscriptionChannel := graphql.Subscribe(subscriptionParams)
for res := range subscriptionChannel {
// ... marshal and flush indefinitely ...
}
There is no limit on the number of concurrent subscriptions, no idle timeout, and no per-client connection cap.
Authenticated users can submit arbitrarily deep and complex GraphQL queries. The GraphQL execution engine processes the full query — consuming CPU and memory for schema validation, field resolution, and error/response formatting — before any Kubernetes API authorization is checked. The request handling in gateway/gateway/endpoint/endpoint.go passes directly to the handler with no query guards:
// gateway/gateway/endpoint/endpoint.go — ServeHTTP()
func (e *Endpoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e.handler == nil || e.handler.Handler == nil {
http.Error(w, "Endpoint not ready", http.StatusServiceUnavailable)
return
}
if r.Header.Get("Accept") == "text/event-stream" {
e.graphqlServer.HandleSubscription(w, r, e.handler.Schema)
return
}
e.handler.Handler.ServeHTTP(w, r)
}
gateway/gateway/graphql/graphql.go — Handler creation with no resource limits; subscription handler with no connection limitsgateway/gateway/endpoint/endpoint.go — Direct passthrough to handler, no query depth/complexity middlewaregateway/gateway/config/config.go — No configuration fields for resource limitsgateway/http/http.go — No rate limiting middlewaregraphql-go/graphql library — No built-in depth/complexity limitinggraphql-go/handler — No resource limit configuration options__schema, __type) should carry elevated costs. Alternatively, consider migrating to a GraphQL library with built-in depth/complexity support (e.g., gqlgen with its complexity extension, or graph-gophers/graphql-go with its MaxDepth option).GraphQL struct in gateway/gateway/config/config.go to expose all resource limits (max query depth, max complexity, max response size, rate limit thresholds) as configurable parameters. This ensures all protective thresholds can be tuned per environment without code changes.HASI2026141-32 — Due: 2026-04-16
{
"cwe_ids": [
"CWE-400",
"CWE-770"
],
"nvd_published_at": null,
"severity": "MODERATE",
"github_reviewed": true,
"github_reviewed_at": "2026-04-08T15:05:10Z"
}