Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ea5167f
feat: inline argument validation
SkArchon Jul 6, 2026
5b04091
fix: updates
SkArchon Jul 7, 2026
030a9c6
fix: updates
SkArchon Jul 7, 2026
2ca67b3
fix: changes
SkArchon Jul 7, 2026
354d8df
fix: gomods
SkArchon Jul 7, 2026
4e191a1
fix: refactoring
SkArchon Jul 7, 2026
55f910b
Merge branch 'main' into milinda/eng-9586-routerengine-force-use-of-v…
SkArchon Jul 7, 2026
0d10a46
fix: updates
SkArchon Jul 7, 2026
91462b5
fix: refactoring
SkArchon Jul 7, 2026
2400d59
fix: gomod
SkArchon Jul 7, 2026
06940db
fix: gomod
SkArchon Jul 7, 2026
1c87b7b
Merge remote-tracking branch 'origin/main' into milinda/eng-9586-rout…
SkArchon Jul 7, 2026
c21fa54
Merge branch 'main' into milinda/eng-9586-routerengine-force-use-of-v…
SkArchon Jul 7, 2026
df494ae
fix: docs
SkArchon Jul 7, 2026
e3a8dcf
fix: gomod
SkArchon Jul 7, 2026
7bf7df0
fix: gomod
SkArchon Jul 7, 2026
e67b4e3
fix: documentation
SkArchon Jul 7, 2026
0bf3160
fix: docs
SkArchon Jul 7, 2026
b060d13
fix: renaming
SkArchon Jul 13, 2026
90e3380
fix: refactoring
SkArchon Jul 13, 2026
8f2cc41
fix: enforcement type
SkArchon Jul 13, 2026
8acbf29
Merge remote-tracking branch 'origin/main' into milinda/eng-9586-rout…
SkArchon Jul 13, 2026
3838938
fix: refactoring
SkArchon Jul 13, 2026
9704b86
fix: update docs
SkArchon Jul 13, 2026
02b9de6
fix: update gomod
SkArchon Jul 14, 2026
6f1b0dd
fix: gomod
SkArchon Jul 14, 2026
3e7b578
fix: gomod
SkArchon Jul 14, 2026
edfc7c3
fix: gomod
SkArchon Jul 14, 2026
3344d46
fix: tests
SkArchon Jul 14, 2026
adc1122
Merge branch 'main' into milinda/eng-9586-routerengine-force-use-of-v…
SkArchon Jul 14, 2026
c8e5f1f
Merge branch 'main' into milinda/eng-9586-routerengine-force-use-of-v…
SkArchon Jul 15, 2026
b8e8c84
fix: gomod
SkArchon Jul 15, 2026
db4a85e
fix: tests
SkArchon Jul 15, 2026
1fbd473
fix: update engine
SkArchon Jul 15, 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/validate-inline-arguments"
]
},
{
Expand Down
29 changes: 29 additions & 0 deletions docs-website/router/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2102,6 +2102,35 @@ engine:
always_skip_loader: false
```

### Validate Inline Arguments

The configuration for [Validate Inline Arguments](/router/security/validate-inline-arguments). Detects, and
optionally rejects, operations that pass argument values inline instead of through variables.

| Environment Variable | YAML | Required | Description | Default Value |
|-----------------------------------------------------------------|---------------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
| ENGINE_VALIDATE_INLINE_ARGUMENTS_MODE | mode | <Icon icon="square" /> | `off` disables the feature; `permissive` detects and records inline arguments while still executing; `strict` rejects operations that use inline argument values. | off |
| ENGINE_VALIDATE_INLINE_ARGUMENTS_ENFORCE_HTTP_STATUS_CODE | enforce_http_status_code | <Icon icon="square" /> | HTTP status code returned when an operation is rejected in enforcing mode. | 400 |
| ENGINE_VALIDATE_INLINE_ARGUMENTS_ERROR_CODE | error_code | <Icon icon="square" /> | The `extensions.code` set on the rejection error. | INLINE_ARGUMENT_VALUES_NOT_ALLOWED |
| ENGINE_VALIDATE_INLINE_ARGUMENTS_ERROR_MESSAGE | error_message | <Icon icon="square" /> | The error message returned to the client on rejection. | Inline argument values are not allowed. Use variables instead. |
| ENGINE_VALIDATE_INLINE_ARGUMENTS_INCLUDE_PERSISTED_OPERATIONS | include_persisted_operations | <Icon icon="square" /> | When true, the policy also applies to persisted operations. Persisted operations are exempt by default. | false |
| ENGINE_VALIDATE_INLINE_ARGUMENTS_RETURN_IN_RESPONSE_EXTENSIONS | return_in_response_extensions | <Icon icon="square" /> | When true, detected inline arguments are returned to the client under `extensions.inlineArguments`. Applies only in non-enforcing mode. | false |

#### Example YAML config:

```yaml config.yaml
version: "1"

engine:
validate_inline_arguments:
mode: permissive
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
return_in_response_extensions: false
```

## Rate Limiting

Configures a rate limiter on the outgoing subgraphs requests. When enabled, a rate of 10 req/s with a burst of 10 requests is configured.
Expand Down
176 changes: 176 additions & 0 deletions docs-website/router/security/validate-inline-arguments.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
title: "Validate Inline Arguments"
description: "Detect, and optionally reject, operations that pass argument values inline instead of through variables."
icon: shield-halved
---

## Overview

An inline argument is an argument whose value is written directly into the operation as a literal
instead of being supplied through a variable.

```graphql
# Inline argument: the value "12345" is hardcoded in the operation.
query GetUser {
userById(userId: "12345") {
name
}
}

# Compliant: the value arrives through a variable.
query GetUser($userId: ID!) {
userById(userId: $userId) {
name
}
}
```

Inline argument values make operations harder to cache and observe. Every distinct literal produces a
distinct operation, which lowers plan cache hit rates. Literals can also embed sensitive data (IDs, tokens,
filter values) directly into query strings, where it ends up in logs and traces. Requiring variables keeps
the operation shape stable and moves the values into the variables payload.

`validate_inline_arguments` detects these arguments during operation normalization. Depending on the mode,
the router logs the findings, returns them to the client, or rejects the operation.

## Modes

The feature has three modes:

| Mode | Behavior |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `off` | The feature is disabled. This is the default. |
| `permissive` | The router detects inline arguments and records them. The operation still executes. Use this to observe traffic before enforcing. |
| `strict` | The router rejects any operation that uses an inline argument value. No subgraph requests are made. |

## Configuration

The feature is configured under `engine.validate_inline_arguments`:

```yaml
engine:
validate_inline_arguments:
mode: permissive
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
return_in_response_extensions: false
```

These options are also listed in more detail in the [router configuration reference](/router/configuration#validate-inline-arguments).

## What counts as an inline argument

Detection runs during normalization and covers every argument whose value is not a variable:

- Field arguments, including introspection fields such as `__type(name: "User")`.
- Directive arguments, including the built-in `@skip` and `@include` and any custom directive.

The following are not flagged:

- Arguments supplied through a variable, for example `userById(userId: $userId)`.
- Variable default values, for example `query($first: Int = 10)`. A default value is not an argument.

Detection happens before `@skip` and `@include` prune the selection set. An inline argument on a field or
directive that normalization later removes is still reported.

## Qualified names

Findings are reported using a qualified name that identifies where the argument was used:

| Context | Format | Example |
| ----------------- | ------------------ | --------------- |
| Field argument | `field.argument` | `userById.userId` |
| Directive argument | `@directive.argument` | `@include.if` |

## Non-enforcing mode

In `permissive` mode the operation executes normally. The router surfaces the findings in two ways.

### Warning log

For every operation that contains inline arguments, the router emits a warning. This happens regardless of
the `return_in_response_extensions` setting.

```json
{
"level": "warn",
"msg": "Inline argument values found in operation; use variables instead",
"count": 2,
"arguments": ["userById.userId", "@include.if"],
"operation_name": "GetUser",
"operation_hash": 1234567890
}
```

### Response extensions

When `return_in_response_extensions` is `true`, the findings are also returned to the client under
`extensions.inlineArguments`. The `count` field is the number of inline arguments, and `arguments` lists their
qualified names.

```json
{
"data": { "userById": { "name": "Me" } },
"extensions": {
"inlineArguments": {
"count": 2,
"arguments": ["userById.userId", "@include.if"]
}
}
}
```

`return_in_response_extensions` has no effect in enforcing mode, because offending operations are rejected
before a response is produced.

## Enforcing mode

In `strict` mode the router rejects any operation that uses an inline argument value. The operation
is rejected during normalization, so no subgraph requests are made.

The rejection is generic. The router stops at the first inline argument and does not name the argument or
point at its location. The response uses the configured `error_message`, `error_code`, and
`enforce_http_status_code`.

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

## Persisted operations

Persisted operations are exempt by default. They are authored and registered ahead of time, so inline values
in a persisted operation are a controlled, reviewable input rather than arbitrary client traffic.

Set `include_persisted_operations: true` to apply the policy to persisted operations as well. This applies to
both non-enforcing and enforcing modes.

## Rollout

Start in `permissive` mode to understand your traffic without breaking clients.

```yaml
engine:
validate_inline_arguments:
mode: permissive
return_in_response_extensions: true
```

Use the warning logs and `extensions.inlineArguments` to find operations that still send inline values and
migrate them to variables. Once the offending operations are gone, switch to `strict`.

```yaml
engine:
validate_inline_arguments:
mode: strict
```
4 changes: 2 additions & 2 deletions router-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/wundergraph/cosmo/router-tests
go 1.25.0

require (
connectrpc.com/connect v1.19.1
connectrpc.com/connect v1.19.2
github.com/MicahParks/jwkset v0.11.0
github.com/buger/jsonparser v1.1.2
github.com/cloudflare/backoff v0.0.0-20240920015135-e46b80a3a7d0
Expand Down Expand Up @@ -31,7 +31,7 @@ require (
github.com/wundergraph/cosmo/router v0.0.0-20260710155145-803a4bc06d92
github.com/wundergraph/cosmo/router-plugin v0.0.0-20250808194725-de123ba1c65e
github.com/wundergraph/cosmo/speedtrap v0.0.0-00010101000000-000000000000
github.com/wundergraph/graphql-go-tools/v2 v2.10.0
github.com/wundergraph/graphql-go-tools/v2 v2.12.1
go.opentelemetry.io/otel v1.44.0
go.opentelemetry.io/otel/sdk v1.44.0
go.opentelemetry.io/otel/sdk/metric v1.44.0
Expand Down
8 changes: 4 additions & 4 deletions router-tests/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo=
connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
connectrpc.com/vanguard v0.3.0 h1:prUKFm8rYDwvpvnOSoqdUowPMK0tRA0pbSrQoMd6Zng=
connectrpc.com/vanguard v0.3.0/go.mod h1:nxQ7+N6qhBiQczqGwdTw4oCqx1rDryIt20cEdECqToM=
github.com/99designs/gqlgen v0.17.76 h1:YsJBcfACWmXWU2t1yCjoGdOmqcTfOFpjbLAE443fmYI=
Expand Down Expand Up @@ -381,8 +381,8 @@ github.com/wundergraph/astjson v1.1.0 h1:xORDosrZ87zQFJwNGe/HIHXqzpdHOFmqWgykCLV
github.com/wundergraph/astjson v1.1.0/go.mod h1:h12D/dxxnedtLzsKyBLK7/Oe4TAoGpRVC9nDpDrZSWw=
github.com/wundergraph/go-arena v1.3.0 h1:n0ng5a1vbd8YGq1u3rMr0vPU5f6AZ1BXIiUhL1UIok8=
github.com/wundergraph/go-arena v1.3.0/go.mod h1:ROOysEHWJjLQ8FSfNxZCziagb7Qw2nXY3/vgKRh7eWw=
github.com/wundergraph/graphql-go-tools/v2 v2.10.0 h1:hAzNsXbzbSTOeD3VcRFeHphLj5S0z5j7F2VaZbtkygs=
github.com/wundergraph/graphql-go-tools/v2 v2.10.0/go.mod h1:rGG9m74sUyucfvSZ83Mjuq/6qRJetl1CVP872f/dCok=
github.com/wundergraph/graphql-go-tools/v2 v2.12.1 h1:wds1aBlnml86PFhgK21sOqzJOb9eOfVF3mK8NLDxEVY=
github.com/wundergraph/graphql-go-tools/v2 v2.12.1/go.mod h1:zREIKLmpjfNcGSubndaW/913r0Y8XbbYOXQeZFkwHdo=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
Expand Down
Loading
Loading