Skip to content
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
be73d6d
feat(router): add disallow inline arguments security policy
gausie Jul 2, 2026
315f6ca
feat(router): add per-client observability for inline argument detection
gausie Jul 2, 2026
b3c4900
feat(router): apply disallow inline arguments policy to websocket ope…
gausie Jul 2, 2026
8791b5d
Merge branch 'main' into worktree-playful-rolling-lecun
gausie Jul 2, 2026
d9f9af9
docs(router): document disallow inline arguments security policy
gausie Jul 2, 2026
8b6ed63
fix(router): handle final write errors and add env overrides for disa…
gausie Jul 2, 2026
f3d440a
test(router): add inline argument detection case for empty input object
gausie Jul 2, 2026
e70d13f
Merge branch 'main' into worktree-playful-rolling-lecun
gausie Jul 2, 2026
24faed2
Merge remote-tracking branch 'origin/main' into worktree-playful-roll…
gausie Jul 3, 2026
05ea1d0
Merge remote-tracking branch 'origin/main' into worktree-playful-roll…
gausie Jul 3, 2026
79649a6
feat(router): check inline arguments on the normalized pre-extraction…
gausie Jul 3, 2026
87d9e25
fix(router): apply disallow inline arguments policy to APQ and reject…
gausie Jul 3, 2026
5b03e6f
refactor(router): store inline arguments checker on the operation pro…
gausie Jul 4, 2026
734f58c
fix(router): skip inline arguments check for schema-invalid operations
gausie Jul 4, 2026
128a45f
refactor(router): reuse shared error types for the inline arguments p…
gausie Jul 4, 2026
5853905
fix(router): validate enforce_http_status_code range at startup
gausie Jul 4, 2026
4dbd4aa
fix(router): record inline arguments error code in the request context
gausie Jul 4, 2026
8f21dcf
fix(router): annotate the initial defer payload with the inline argum…
gausie Jul 4, 2026
ad314a8
fix(router): classify registered persisted operations sent as body pl…
gausie Jul 4, 2026
b67ca32
test(router): cover batching and introspection in disallow inline arg…
gausie Jul 4, 2026
b283df6
refactor(router): simplify disallow inline arguments after review
gausie Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs-website/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@
"router/security/tls",
"router/security/config-validation-and-signing",
"router/security/hardening-guide",
"router/security/cost-control"
"router/security/cost-control",
"router/security/disallow-inline-arguments"
]
},
{
Expand Down
12 changes: 12 additions & 0 deletions docs-website/router/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2286,6 +2286,12 @@ The configuration for the security. The security is used to configure the securi
| | parser_limits.approximate_depth_limit | <Icon icon="square" /> | The approximate cumulative depth limit of a query, including fragments. Set to 0 to disable. | 200 |
| | parser_limits.total_fields_limit | <Icon icon="square" /> | The total number of fields the parser will allow. Set to 0 to disable. | 3500 |
| SECURITY_OPERATION_NAME_LENGTH_LIMIT | operation_name_length_limit | <Icon icon="square" /> | The maximum allowed length for the operation name. Set to 0 to disable. | 512 |
| SECURITY_DISALLOW_INLINE_ARGUMENTS | disallow_inline_arguments | <Icon icon="square" /> | Detect or reject operations that hardcode argument values instead of using variables. See [Disallow Inline Arguments](/router/security/disallow-inline-arguments). | |
| SECURITY_DISALLOW_INLINE_ARGUMENTS_MODE | disallow_inline_arguments.mode | <Icon icon="square" /> | `off` disables the check; `warn` logs and annotates the response; `enforce` rejects the operation. | off |
| SECURITY_DISALLOW_INLINE_ARGUMENTS_ENFORCE_HTTP_STATUS_CODE | disallow_inline_arguments.enforce_http_status_code | <Icon icon="square" /> | The HTTP status code returned in enforce mode. Use 200 for strict GraphQL-spec compliance. | 400 |
| SECURITY_DISALLOW_INLINE_ARGUMENTS_ERROR_CODE | disallow_inline_arguments.error_code | <Icon icon="square" /> | The GraphQL error `extensions.code` emitted on rejection or annotation. | INLINE_ARGUMENT_VALUES_NOT_ALLOWED |
| SECURITY_DISALLOW_INLINE_ARGUMENTS_ERROR_MESSAGE | disallow_inline_arguments.error_message | <Icon icon="square" /> | The human-readable error or hint message. | Inline argument values are not allowed. Use variables instead. |
| SECURITY_DISALLOW_INLINE_ARGUMENTS_INCLUDE_PERSISTED_OPERATIONS | disallow_inline_arguments.include_persisted_operations | <Icon icon="square" /> | When true, the policy also applies to persisted operations. By default they are exempt. | false |


### Example YAML Configuration
Expand Down Expand Up @@ -2328,6 +2334,12 @@ security:
approximate_depth_limit: 300
total_fields_limit: 700
operation_name_length_limit: 1024
disallow_inline_arguments:
mode: warn # off | warn | enforce
enforce_http_status_code: 400
error_code: "INLINE_ARGUMENT_VALUES_NOT_ALLOWED"
error_message: "Inline argument values are not allowed. Use variables instead."
include_persisted_operations: false
```

<Warning>
Expand Down
139 changes: 139 additions & 0 deletions docs-website/router/security/disallow-inline-arguments.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: "Disallow Inline Arguments"
description: "Detect or reject GraphQL operations that hardcode argument values instead of using variables"
icon: brackets-curly
---

## Overview

Inline argument values embed request data directly in the query document:

```graphql
query {
employee(id: 1) {
id
}
}
```

The same operation written with variables keeps the document stable across requests:

```graphql
query ($id: Int!) {
employee(id: $id) {
id
}
}
```

Hardcoded values cause practical problems at scale. Every distinct value produces a distinct document, which defeats operation-level caching and inflates normalization and plan cache cardinality. Inline values also leak request data such as user IDs into query documents, where they end up in logs and traces. Operations with inline values cannot be registered as persisted operations without one registration per value.

The `security.disallow_inline_arguments` policy detects inline values in field arguments and directive arguments. Depending on the configured mode, the router logs them, annotates the response, or rejects the operation. Detection runs on the normalized operation, before inline values are extracted into variables, so it checks exactly what the router will execute: inline values in a non-executed sibling operation of a multi-operation document, in an unused fragment, or in a statically resolved `@skip`/`@include` directive are not flagged, because normalization removes them from the executed operation.

Values supplied through variables are compliant. This includes variable-definition default values and variable usage in directives such as `@include(if: $flag)`. A literal directive argument on a directive that survives normalization is flagged; `@include(if: true)` and `@skip(if: false)` are resolved away by normalization and therefore compliant.

## Modes

| Mode | Behavior |
| --------- | ----------------------------------------------------------------------------------------------------------- |
| `off` | The check is disabled. This is the default. |
| `warn` | The operation executes normally. The router logs a warning and annotates the response with an extensions hint. |
| `enforce` | The router rejects the operation before execution. |

The recommended migration path is to run `warn` mode first, identify offending clients from the logs, migrate them to variables, and then switch to `enforce`.

## Configuration

```yaml
security:
disallow_inline_arguments:
mode: warn # off | warn | enforce
enforce_http_status_code: 400
error_code: "INLINE_ARGUMENT_VALUES_NOT_ALLOWED"
error_message: "Inline argument values are not allowed. Use variables instead."
include_persisted_operations: false
```

| Option | Default | Description |
| ------------------------------ | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `mode` | `off` | Controls the policy behavior. One of `off`, `warn`, `enforce`. |
| `enforce_http_status_code` | `400` | The HTTP status code returned in enforce mode. Use `200` for strict GraphQL-spec compliance. |
| `error_code` | `INLINE_ARGUMENT_VALUES_NOT_ALLOWED` | The `extensions.code` value emitted on rejection or annotation. |
| `error_message` | `Inline argument values are not allowed. Use variables instead.` | The human-readable error or hint message. |
| `include_persisted_operations` | `false` | When `true`, the policy also applies to persisted operations. |

Each option has an environment variable equivalent: `SECURITY_DISALLOW_INLINE_ARGUMENTS_MODE`, `SECURITY_DISALLOW_INLINE_ARGUMENTS_ENFORCE_HTTP_STATUS_CODE`, `SECURITY_DISALLOW_INLINE_ARGUMENTS_ERROR_CODE`, `SECURITY_DISALLOW_INLINE_ARGUMENTS_ERROR_MESSAGE`, and `SECURITY_DISALLOW_INLINE_ARGUMENTS_INCLUDE_PERSISTED_OPERATIONS`.

## Warn Mode

In warn mode the operation executes normally and the response gains an `extensions.inlineArguments` annotation listing every offending argument by name and value kind. For the request `{ employee(id: 1) { id } }` the response is:

```json
{
"data": {
"employee": {
"id": 1
}
},
"extensions": {
"inlineArguments": {
"code": "INLINE_ARGUMENT_VALUES_NOT_ALLOWED",
"message": "Inline argument values are not allowed. Use variables instead.",
"arguments": [
{ "argument": "id", "valueKind": "Int" }
]
}
}
}
```

The router also emits a structured warning log, `inline arguments found in operation`, with the argument count, argument names, operation name, and the client name and version. Use these fields to build a per-client breakdown of inline argument usage before switching to enforce mode.

<Info>
Subscription responses stream and cannot carry a response annotation. For
subscriptions and for operations sent over WebSocket connections, warn mode
logs only. Queries using `@defer` receive the annotation in the `extensions`
of the initial multipart payload.
</Info>

## Enforce Mode

In enforce mode the router rejects the operation with the configured HTTP status code during validation, before planning and execution. No subgraph requests are made. A schema-invalid operation is rejected with its schema-validation error first; this policy applies to valid operations. The same request is rejected with:

```json
{
"errors": [
{
"message": "Inline argument values are not allowed. Use variables instead.",
"extensions": {
"code": "INLINE_ARGUMENT_VALUES_NOT_ALLOWED",
"inlineArguments": {
"code": "INLINE_ARGUMENT_VALUES_NOT_ALLOWED",
"message": "Inline argument values are not allowed. Use variables instead.",
"arguments": [
{ "argument": "id", "valueKind": "Int" }
]
}
}
}
]
}
```

The top-level `extensions.code` supports client and APM error classification. The nested `inlineArguments` object carries the full detail for debugging.

## Transports

The policy applies to HTTP requests and WebSocket operations.

Over WebSocket connections, enforce mode rejects the operation with a terminal `error` message carrying the same `extensions` payload as the HTTP response. The `enforce_http_status_code` option does not apply because WebSocket errors travel as protocol messages.

## Persisted Operations

Registered persisted operations are exempt by default. They are stored server-side and their contents are intentional. Set `include_persisted_operations: true` to apply the policy to them as well.

Automatic persisted queries (APQ) are never exempt. Their hashes are computed by the client, so they carry no operator intent and are checked like ad-hoc operations.

## Telemetry

When inline arguments are detected on an HTTP request, the router sets the `wg.operation.inline_arguments.count` attribute on the router root span, in both warn and enforce mode. Combine it with the client attributes on the same span to track which clients still send inline arguments.
Loading
Loading