Subgraph Error Masking
Error Masking prevents internal subgraph errors from leaking to clients. Subgraphs are internal services — the router sits between them and the outside world, so subgraph errors are rewritten into a safe, generic form before they reach clients.
Because the router masks on their behalf, your subgraphs don't need to sanitize their own errors: they can return rich, detailed errors for observability, and trust the router to redact them at the edge.
Error masking is on by default. Out of the box, every subgraph error
message is replaced with "Unexpected error", so no internal message reaches
clients unless you opt out.
Why mask subgraph errors?
GraphQL errors from subgraphs frequently carry information that is useful internally but dangerous to expose publicly:
- Sensitive detail leakage — database errors, stack traces, internal hostnames, or upstream HTTP
response bodies embedded in an error message or its
extensions. - Implementation disclosure — error codes and service names reveal your internal topology and the technologies each subgraph uses, giving attackers a map of your system.
- Inconsistent client contract — different subgraphs phrase and structure errors differently. Masking gives clients a single, predictable error shape.
How it works
An error is treated as a subgraph error when it carries an extensions.service field. This covers
two sources:
- Subgraph-returned errors — errors a subgraph includes in its GraphQL response.
- Router-synthesized errors — errors the router generates when a subgraph call fails at the
network or HTTP layer (for example
SUBREQUEST_HTTP_ERRORorDOWNSTREAM_SERVICE_ERROR).
Errors produced by the router itself that are unrelated to a subgraph — such as a
GRAPHQL_PARSE_FAILED validation error — are never masked, since they carry no service field.
When masking applies, the router can:
- Replace the error
messagewith a fixed, safe string (default"Unexpected error"). - Redact the error
extensionsvia an allowlist or denylist of keys.
Both are independent and configurable globally or per subgraph.
Masking runs last in the response pipeline — after metrics, tracing, and logging. Your telemetry always records the original, unmasked error; only the client-facing response is redacted.
What is masked by default
| Part | Default behavior |
|---|---|
message | Replaced with "Unexpected error". |
extensions | Not touched — passed through as-is. |
By default only the message is masked. Anything sensitive in extensions
still reaches the client. If your subgraphs put internal detail in
extensions, configure extensions masking as well — see the configuration
reference.
Configuration
All settings live under the error_masking key. A few
common setups:
Strictest — hide message and all extensions:
error_masking:
all:
error_message: true
extensions:
mode: allow
keys: [] # allow noneKeep only the error code for client-side handling:
error_masking:
all:
error_message: true
extensions:
mode: allow
keys:
- codeDisable masking entirely (e.g. during development):
error_masking:
all:
error_message: falseOr via environment variable, without editing the config:
DISABLE_SUBGRAPH_ERROR_MASKING=trueSee the error_masking configuration reference for the
full set of options, per-subgraph overrides, and more examples.