# GraphQL and gRPC Security Review Patterns

> **Intro:** GraphQL and gRPC compress a lot of power into fewer, richer interfaces. That improves developer experience, but it also changes how discovery, authorization, and resource-abuse review must work.
>
> **What this page includes**
>
> * GraphQL-specific controls for complexity, introspection, and resolver authorization
> * gRPC-specific controls for mTLS, metadata auth, deadlines, streaming, and reflection exposure
> * example configs and reviewer prompts

## Why these protocols need dedicated review

Neither GraphQL nor gRPC fails in exactly the same way as path-based REST.

* **GraphQL** tends to fail through **schema discoverability, nested query cost, batching, and resolver-level authorization gaps**.
* **gRPC** tends to fail through **service overexposure, weak transport/auth configuration, missing deadlines/cancellation, and unsafe streaming assumptions**.

## Part 1 — GraphQL

### Core security concerns

| Topic                          | What to review                                                                                   |
| ------------------------------ | ------------------------------------------------------------------------------------------------ |
| Introspection                  | is production schema discoverability restricted where appropriate?                               |
| Depth limits                   | can nested queries recurse deeply enough to hurt availability?                                   |
| Complexity / cost limits       | can one request trigger disproportionate backend work?                                           |
| Resolver authorization         | are authz checks enforced in nested resolvers and object fetches, not just top-level operations? |
| Pagination                     | are list-returning fields bounded and predictable?                                               |
| Persisted queries              | can you reduce arbitrary query submission in production?                                         |
| Error handling                 | do validation / resolver errors leak internals?                                                  |
| Batched requests / alias abuse | can one HTTP request trigger many logical expensive operations?                                  |

### Review rule #1 — treat resolver auth as the real auth surface

Path-based API review is not enough. In GraphQL, a safe top-level mutation can still leak data if nested fields, object loaders, or child resolvers skip object-level or tenant-level authorization.

### Review rule #2 — depth limits alone are not enough

A shallow query can still be expensive if it requests many lists, aliases, or fields backed by slow joins, fan-out service calls, or heavy aggregations.

Use both:

* **depth limit**, and
* **complexity / cost limit**.

### Example: Apollo-style baseline

```ts
import { ApolloServer } from '@apollo/server';
import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: false, // for non-public production graphs
  validationRules: [
    depthLimit(6),
    createComplexityLimitRule(1000)
  ]
});
```

### Production control table for GraphQL

| Control            | Good default                                                        |
| ------------------ | ------------------------------------------------------------------- |
| Introspection      | disable for non-public production graphs; document exceptions       |
| Persisted queries  | prefer allow-listed / persisted operations for high-value clients   |
| Pagination         | mandatory on list fields above trivial scope                        |
| Resolver auth      | enforce at field/object level where data sensitivity requires it    |
| Complexity budgets | set limits based on backend cost, not aesthetics                    |
| Rate limiting      | actor-aware and operation-aware                                     |
| Logging            | operation name, caller, depth, cost, denials, latency, error family |

## Part 2 — gRPC

### Core security concerns

| Topic                       | What to review                                                                          |
| --------------------------- | --------------------------------------------------------------------------------------- |
| TLS / mTLS                  | are channels encrypted and, where needed, mutually authenticated?                       |
| Auth metadata               | how are identities or bearer credentials attached and validated?                        |
| Method-level authz          | is access enforced per RPC method and resource?                                         |
| Reflection                  | is server reflection enabled in production without a reason?                            |
| Streaming security          | are long-lived streams bounded, cancellable, and authenticated for their full lifetime? |
| Deadlines                   | do clients set them, or can calls hang forever?                                         |
| Cancellation propagation    | do servers and downstreams stop work when the client is gone?                           |
| Flow control / message size | can clients overwhelm servers with large or endless streams?                            |
| Keepalive                   | are settings coordinated to avoid self-inflicted instability or abuse?                  |

### gRPC transport baseline

* require **TLS** everywhere;
* use **mTLS** for internal service-to-service trust where workload identity matters;
* do not rely only on network location;
* keep **channel auth** separate from **RPC authorization**.

### Example: Go gRPC client with TLS and deadline

```go
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

creds := credentials.NewTLS(&tls.Config{
    MinVersion: tls.VersionTLS13,
})

conn, err := grpc.DialContext(
    ctx,
    "billing.internal:443",
    grpc.WithTransportCredentials(creds),
)
if err != nil {
    return err
}
defer conn.Close()
```

### Streaming security

Streaming RPCs deserve explicit review because they are long-lived and stateful.

#### What can go wrong

* no deadline or cancellation handling, so streams sit open indefinitely;
* server keeps processing after the client disconnects;
* backpressure assumptions are wrong and resource use grows unbounded;
* auth is checked only once, but long-lived stream semantics change over time;
* message size or event rate is not bounded;
* keepalive is set too aggressively and becomes a reliability or DoS problem.

#### Streaming checklist

* [ ] realistic deadlines or maximum session durations are set
* [ ] server handles cancellation and stops downstream work
* [ ] message size and rate limits exist
* [ ] flow control is not bypassed accidentally by buffering everything in memory
* [ ] authorization assumptions for the stream are documented
* [ ] reflection is restricted if not needed

### gRPC reviewer prompts

* Is server reflection enabled in production? Why?
* Can a bearer token be replayed across services because metadata is forwarded too freely?
* What stops a client from opening many streams and idling them?
* Which RPC methods are internet-facing, and which are strictly internal?
* If a stream is authorized at start, what happens when tenant context or entitlements change during its lifetime?

## GraphQL vs gRPC quick comparison

| Topic                  | GraphQL                                          | gRPC                                                                 |
| ---------------------- | ------------------------------------------------ | -------------------------------------------------------------------- |
| Discoverability risk   | introspection / schema docs                      | reflection / service listing                                         |
| Main abuse risk        | deep / expensive query shapes                    | unbounded streams, no deadlines, message / concurrency abuse         |
| Auth mistake pattern   | top-level auth only, nested resolvers leak       | transport auth assumed sufficient, method authz missing              |
| Performance guardrails | depth, complexity, pagination, persisted queries | deadlines, flow control, message-size limits, cancellation           |
| Logging focus          | operation name, cost, depth, resolver failures   | method, peer identity, deadline exceeded, cancellation, stream stats |

## Recommended operating pattern

### For GraphQL

* disable introspection in non-public production graphs unless explicitly needed;
* use persisted queries or allow-listing for high-value clients;
* enforce depth + complexity + pagination;
* test resolver authorization below the top level.

### For gRPC

* require TLS and strongly prefer mTLS for internal service trust;
* set deadlines by default;
* propagate cancellation;
* review streaming RPCs as resource-management and authorization problems, not just protocol features.

## Related pages

* [GraphQL Security Review and Abuse Patterns](/architecture-api-crypto-and-identity/index/graphql-security-review-and-abuse-patterns.md)
* [API Authentication and Authorization](/architecture-api-crypto-and-identity/index/api-authentication-and-authorization.md)
* [API Abuse Resilience and Rate Limits](/architecture-api-crypto-and-identity/index/api-abuse-resilience-and-rate-limits.md)
* [Internal PKI for Microservices — mTLS and Certificate Automation](/cloud-kubernetes-and-infrastructure-security/index/internal-pki-for-microservices-mtls-and-certificate-automation.md)

***

*Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.*


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.product-security.expert/architecture-api-crypto-and-identity/index/graphql-and-grpc-security-review-patterns.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
