From b3e94d603c85fcee6d783b6dacf29c06a78f7990 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:35:46 +0200 Subject: [PATCH 01/20] add initial draft --- docs/docs.json | 3 +- docs/router/security/cost-control.mdx | 301 ++++++++++++++++++++++++++ 2 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 docs/router/security/cost-control.mdx diff --git a/docs/docs.json b/docs/docs.json index 199ef4f5..d109df14 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -200,7 +200,8 @@ "router/security", "router/security/tls", "router/security/config-validation-and-signing", - "router/security/hardening-guide" + "router/security/hardening-guide", + "router/security/cost-control" ] }, { diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx new file mode 100644 index 00000000..27f19fed --- /dev/null +++ b/docs/router/security/cost-control.mdx @@ -0,0 +1,301 @@ +--- +title: "Cost Analysis" +description: "Protect your API from expensive operations by estimating query complexity before execution using @cost and @listSize directives" +icon: scale-balanced +--- + +## Overview + +Some GraphQL queries are more expensive than others. A query requesting deeply nested lists can generate +thousands of resolver calls and overwhelm your subgraphs, while a simple field lookup costs almost nothing. +Cost analysis lets you assign weights to fields and estimate query complexity *before* execution begins. + +When cost analysis is enabled, the router calculates an estimated cost for each incoming operation based +on the fields requested, their configured weights, and expected list sizes. +Operations exceeding your limit are rejected immediately, no subgraph requests are made, +protecting your infrastructure from resource exhaustion. + +This feature implements the [IBM GraphQL Cost Directive Specification](https://ibm.github.io/graphql-specs/cost-spec.html), +adapted for GraphQL Federation. + +## How Cost is Calculated + +The router walks through your query and sums up the cost of each field. By default: + +- **Object types** (including interfaces and unions) cost **1** +- **Scalar and enum fields** cost **0** + +For example, this query has a cost of 4: + +```graphql +query { + book(id: 1) { # Book object: 1 + title # String scalar: 0 + author { # Author object: 1 + name # String scalar: 0 + } + publisher { # Publisher object: 1 + address { # Address object: 1 + zipCode # Int scalar: 0 + } + } + } +} +``` + +### List Fields Multiply Cost + +When a field returns a list, the cost of that field and all its children is multiplied by the expected list size. +Since the router doesn't know actual list sizes at planning time, it uses estimates. + +```graphql +query { + employees { # List of Employee + id + department { + name + } + } +} +``` + +With a default list size of 10, this query costs: `10 × (1 Employee + 1 Department) = 20` + +## Configuration + +Enable cost analysis in your router configuration: + +```yaml +security: + cost_analysis: + enabled: true + static_limit: 1000 + list_size: 10 +``` + +### Configuration Options + +**`enabled`** — When true, the router calculates static cost for every operation. This is required to use `static_limit` or to access cost from custom modules. + +**`static_limit`** — The maximum allowed cost. Operations exceeding this limit receive a 400 error before execution. Set to 0 to calculate costs without enforcing a limit (useful for monitoring). + +**`list_size`** — The default assumed size for list fields when no `@listSize` directive is specified. Choose a value that reflects typical list sizes in your API. + +### Environment Variables + +All options can be configured via environment variables: + +| Option | Environment Variable | +|--------|---------------------| +| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | +| `static_limit` | `SECURITY_COST_ANALYSIS_STATIC_LIMIT` | +| `list_size` | `SECURITY_COST_ANALYSIS_LIST_SIZE` | + +## Customizing Cost with Directives + +Default weights work for many APIs, but you can fine-tune cost calculation using the `@cost` and `@listSize` directives in your schema. + +### The @cost Directive + +Use `@cost` to assign custom weights to types, fields, or arguments that are more expensive than average. + +**On a type** — All fields returning this type inherit the weight: + +```graphql +type Address @cost(weight: 5) { + street: String + city: String + zipCode: String +} +``` + +**On a field** — Override the cost for a specific field: + +```graphql +type Query { + search(term: String!): [Result] @cost(weight: 10) +} +``` + +**On an argument** — Add cost for expensive argument processing: + +```graphql +type Query { + users(filter: UserFilter @cost(weight: 3)): [User] +} +``` + +### The @listSize Directive + +Use `@listSize` to provide better list size estimates than the global default. + +**Static size with `assumedSize`** — When you know a field always returns roughly the same number of items: + +```graphql +type Query { + topProducts: [Product] @listSize(assumedSize: 5) + featuredCategories: [Category] @listSize(assumedSize: 3) +} +``` + +**Dynamic size with `slicingArguments`** — When the list size is controlled by a pagination argument: + +```graphql +type Query { + products(first: Int, after: String): ProductConnection + @listSize(slicingArguments: ["first"]) + + searchResults(limit: Int!, offset: Int): [Result] + @listSize(slicingArguments: ["limit"]) +} +``` + +The router reads the argument value from the query to determine the multiplier: + +```graphql +query { + products(first: 20) { # Multiplier is 20 + edges { node { name } } + } +} +``` + +When multiple slicing arguments are provided, the router uses the maximum value among them. + +## Accessing Cost in Custom Modules + +When building custom modules, you can access the calculated cost through the operation context: + +```go +func (m *MyModule) Middleware(ctx core.RequestContext, next http.Handler) { + // Get the static cost after planning + cost, err := ctx.Operation().StaticCost() + if err != nil { + // Cost analysis not enabled or plan not ready + } + + // Use cost for custom logic (rate limiting, logging, etc.) + if cost > m.warningThreshold { + m.logger.Warn("High cost operation", "cost", cost) + } + + next.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) +} +``` + +This enables use cases like: + +- Custom rate limiting based on query cost +- Logging expensive operations for analysis +- Billing based on query complexity +- Dynamic throttling during high load + +## Error Responses + +When a query exceeds the static limit, the router returns a 400 status with an error: + +```json +{ + "errors": [ + { + "message": "operation cost 1500 exceeds the maximum allowed static cost of 1000" + } + ] +} +``` + +## Differences from Apollo Demand Control + +If you're familiar with Apollo Router's demand control feature, here are the key differences: + +| Aspect | Apollo | Cosmo | +|--------|--------|-------| +| Configuration location | `demand_control` | `security.cost_analysis` | +| Limit setting | `strategy.static_estimated.max` | `static_limit` | +| Measure-only mode | `mode: measure` | Set `static_limit: 0` with `enabled: true` | + +### Features Not Yet Implemented + +The following features from Apollo's demand control are not yet available: + +- **Actual cost measurement** — Measuring real cost after execution based on actual list sizes returned by subgraphs +- **Cost delta** — The difference between estimated and actual cost +- **Cost telemetry** — OTEL metrics and span attributes for cost values (`cost.estimated`, `cost.actual`, `cost.delta`) +- **`sizedFields` in @listSize** — Applying list size estimates to specific child fields rather than all list children +- **Operation type base costs** — Extra base cost for mutations (Apollo adds 10 for mutations) +- **Response header exposure** — Exposing cost values in response headers via Rhai scripts + +## Best Practices + +### Start with Monitoring + +Enable cost analysis with `static_limit: 0` first. This calculates costs without rejecting any queries, allowing you to understand your traffic patterns before setting limits. + +```yaml +security: + cost_analysis: + enabled: true + static_limit: 0 # Monitor only + list_size: 10 +``` + +### Choose list_size Carefully + +The default list size significantly impacts cost calculations. If your lists typically return 5-20 items, a default of 10 is reasonable. If you have fields that return large lists (100+ items), consider: + +1. Using `@listSize(assumedSize: N)` on those specific fields +2. Requiring pagination with `slicingArguments` + +### Annotate Expensive Resolvers + +Use `@cost` on fields that trigger expensive operations: + +- External API calls +- Complex computations +- Large database queries +- Fields that fan out to multiple subgraphs + +```graphql +type Query { + # Calls external payment API + paymentHistory(userId: ID!): [Payment] @cost(weight: 5) + + # Requires joining multiple tables + analyticsReport(dateRange: DateRange!): Report @cost(weight: 10) +} +``` + +### Design for Pagination + +APIs with pagination provide better cost estimation. Use `slicingArguments` to give the router accurate multipliers based on what clients actually request: + +```graphql +type Query { + # Good: cost scales with requested page size + users(first: Int!, after: String): UserConnection + @listSize(slicingArguments: ["first"]) + + # Less ideal: cost uses default list_size + allUsers: [User] +} +``` + +### Nested Lists + +Be especially careful with nested list fields, as costs multiply: + +```graphql +query { + departments { # 10 departments + employees { # × 10 employees each = 100 + projects { # × 10 projects each = 1000 + tasks { # × 10 tasks each = 10000 + name + } + } + } + } +} +``` + +With default list size of 10, this query has a cost of over 10,000. Use `@listSize` to provide realistic estimates for deeply nested structures. From 47761b10395dc379eeff631a22ff9c77f0028a4d Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:25:27 +0200 Subject: [PATCH 02/20] fix the start --- docs/router/security/cost-control.mdx | 78 ++++++++++++++++++--------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 27f19fed..e510178e 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -1,31 +1,44 @@ --- title: "Cost Analysis" -description: "Protect your API from expensive operations by estimating query complexity before execution using @cost and @listSize directives" +description: "Protect the API from expensive operations by estimating query complexity before execution using @cost and @listSize directives" icon: scale-balanced --- ## Overview Some GraphQL queries are more expensive than others. A query requesting deeply nested lists can generate -thousands of resolver calls and overwhelm your subgraphs, while a simple field lookup costs almost nothing. -Cost analysis lets you assign weights to fields and estimate query complexity *before* execution begins. +thousands of resolver calls and overwhelm subgraphs, while a simple field lookup costs almost nothing. +Cost analysis allows assigning weights to fields and estimating query complexity *before* execution begins. When cost analysis is enabled, the router calculates an estimated cost for each incoming operation based on the fields requested, their configured weights, and expected list sizes. -Operations exceeding your limit are rejected immediately, no subgraph requests are made, -protecting your infrastructure from resource exhaustion. +Operations exceeding the configured limit are rejected immediately — no subgraph requests are made, +protecting the infrastructure from resource exhaustion. This feature implements the [IBM GraphQL Cost Directive Specification](https://ibm.github.io/graphql-specs/cost-spec.html), adapted for GraphQL Federation. -## How Cost is Calculated +### Key differences from the IBM specification + +Specification does not take into account the federation aspect. Some implementation details to +explain how we calculate static costs. Static cost value is based on the query plan. Query plan +use a federation of subgraphs to create a chain of fetches. This chain of fetches is invisible +(on the supergraph level) and could happen because of entity calls, `@requires` fetches, etc. +We account for those because they all make some type of queries more expensive. + +We do not use stringified floats for weights, but rather plan integer values. -The router walks through your query and sums up the cost of each field. By default: +Router supports calculation of static (estimated) costs only at this moment. +Dynamic (runtime) cost calculated on the actual data returned from the subgraphs, is in active +development. + +## How Cost is Calculated -- **Object types** (including interfaces and unions) cost **1** -- **Scalar and enum fields** cost **0** +The router walks through the query and sums up the cost of each field. This is a simplified abstraction, +but this should be used to estimate how expensive queries will be as a basis. -For example, this query has a cost of 4: +By default, object types (including interfaces and unions) cost `1`, scalar and enum fields cost `0`. +For example, this query has a cost of `4`: ```graphql query { @@ -42,6 +55,9 @@ query { } } ``` +In reality, router takes into account the possible weights assigned to the same field coordinate +in different subgraphs. +It is possible to make particular field resolvers on particular subgraphs more expensive. ### List Fields Multiply Cost @@ -63,7 +79,7 @@ With a default list size of 10, this query costs: `10 × (1 Employee + 1 Departm ## Configuration -Enable cost analysis in your router configuration: +Cost analysis is enabled in the router configuration: ```yaml security: @@ -75,11 +91,15 @@ security: ### Configuration Options -**`enabled`** — When true, the router calculates static cost for every operation. This is required to use `static_limit` or to access cost from custom modules. +**`enabled`** — When true, the router calculates static cost for every operation. +This is required to use `static_limit` or to access cost from custom modules. -**`static_limit`** — The maximum allowed cost. Operations exceeding this limit receive a 400 error before execution. Set to 0 to calculate costs without enforcing a limit (useful for monitoring). +**`static_limit`** — The maximum allowed cost. +Operations exceeding this limit receive a 400 error before execution. +Set to 0 to calculate costs without enforcing a limit (useful for monitoring). -**`list_size`** — The default assumed size for list fields when no `@listSize` directive is specified. Choose a value that reflects typical list sizes in your API. +**`list_size`** — The default assumed size for list fields when no `@listSize` directive is specified. +Should reflect typical list sizes in the API. ### Environment Variables @@ -93,11 +113,12 @@ All options can be configured via environment variables: ## Customizing Cost with Directives -Default weights work for many APIs, but you can fine-tune cost calculation using the `@cost` and `@listSize` directives in your schema. +Default weights work for many APIs, +but cost calculation can be fine-tuned using the `@cost` and `@listSize` directives in the schema. ### The @cost Directive -Use `@cost` to assign custom weights to types, fields, or arguments that are more expensive than average. +`@cost` assigns custom weights to types, fields, or arguments that are more expensive than average. **On a type** — All fields returning this type inherit the weight: @@ -127,9 +148,9 @@ type Query { ### The @listSize Directive -Use `@listSize` to provide better list size estimates than the global default. +`@listSize` provides better list size estimates than the global default. -**Static size with `assumedSize`** — When you know a field always returns roughly the same number of items: +**Static size with `assumedSize`** — When a field always returns roughly the same number of items: ```graphql type Query { @@ -164,7 +185,7 @@ When multiple slicing arguments are provided, the router uses the maximum value ## Accessing Cost in Custom Modules -When building custom modules, you can access the calculated cost through the operation context: +In custom modules, the calculated cost is accessible through the operation context: ```go func (m *MyModule) Middleware(ctx core.RequestContext, next http.Handler) { @@ -206,7 +227,7 @@ When a query exceeds the static limit, the router returns a 400 status with an e ## Differences from Apollo Demand Control -If you're familiar with Apollo Router's demand control feature, here are the key differences: +For those familiar with Apollo Router's demand control feature, here are the key differences: | Aspect | Apollo | Cosmo | |--------|--------|-------| @@ -229,7 +250,8 @@ The following features from Apollo's demand control are not yet available: ### Start with Monitoring -Enable cost analysis with `static_limit: 0` first. This calculates costs without rejecting any queries, allowing you to understand your traffic patterns before setting limits. +Start with `static_limit: 0` to calculate costs without rejecting any queries. +This allows traffic patterns to be understood before setting limits. ```yaml security: @@ -241,14 +263,16 @@ security: ### Choose list_size Carefully -The default list size significantly impacts cost calculations. If your lists typically return 5-20 items, a default of 10 is reasonable. If you have fields that return large lists (100+ items), consider: +The default list size significantly impacts cost calculations. +If lists typically return 5-20 items, a default of 10 is reasonable. +For fields that return large lists (100+ items), consider: 1. Using `@listSize(assumedSize: N)` on those specific fields 2. Requiring pagination with `slicingArguments` ### Annotate Expensive Resolvers -Use `@cost` on fields that trigger expensive operations: +`@cost` should be applied to fields that trigger expensive operations: - External API calls - Complex computations @@ -267,7 +291,8 @@ type Query { ### Design for Pagination -APIs with pagination provide better cost estimation. Use `slicingArguments` to give the router accurate multipliers based on what clients actually request: +APIs with pagination provide better cost estimation. +`slicingArguments` gives the router accurate multipliers based on what clients actually request: ```graphql type Query { @@ -282,7 +307,7 @@ type Query { ### Nested Lists -Be especially careful with nested list fields, as costs multiply: +Nested list fields require special attention, as costs multiply: ```graphql query { @@ -298,4 +323,5 @@ query { } ``` -With default list size of 10, this query has a cost of over 10,000. Use `@listSize` to provide realistic estimates for deeply nested structures. +With default list size of 10, this query has a cost of over 10,000. +`@listSize` should be used to provide realistic estimates for deeply nested structures. From 487fd960531b62824df5643862336718dec06ba4 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:06:44 +0200 Subject: [PATCH 03/20] do first full pass --- docs/router/security/cost-control.mdx | 81 +++++++++------------------ 1 file changed, 27 insertions(+), 54 deletions(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index e510178e..3460bb06 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -26,7 +26,7 @@ use a federation of subgraphs to create a chain of fetches. This chain of fetche (on the supergraph level) and could happen because of entity calls, `@requires` fetches, etc. We account for those because they all make some type of queries more expensive. -We do not use stringified floats for weights, but rather plan integer values. +We do not use stringified floats for weights but rather plan integer values. Router supports calculation of static (estimated) costs only at this moment. Dynamic (runtime) cost calculated on the actual data returned from the subgraphs, is in active @@ -35,7 +35,7 @@ development. ## How Cost is Calculated The router walks through the query and sums up the cost of each field. This is a simplified abstraction, -but this should be used to estimate how expensive queries will be as a basis. +but this should be used as a basis to estimate how expensive queries are. By default, object types (including interfaces and unions) cost `1`, scalar and enum fields cost `0`. For example, this query has a cost of `4`: @@ -89,27 +89,11 @@ security: list_size: 10 ``` -### Configuration Options - -**`enabled`** — When true, the router calculates static cost for every operation. -This is required to use `static_limit` or to access cost from custom modules. - -**`static_limit`** — The maximum allowed cost. -Operations exceeding this limit receive a 400 error before execution. -Set to 0 to calculate costs without enforcing a limit (useful for monitoring). - -**`list_size`** — The default assumed size for list fields when no `@listSize` directive is specified. -Should reflect typical list sizes in the API. - -### Environment Variables - -All options can be configured via environment variables: - -| Option | Environment Variable | -|--------|---------------------| -| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | -| `static_limit` | `SECURITY_COST_ANALYSIS_STATIC_LIMIT` | -| `list_size` | `SECURITY_COST_ANALYSIS_LIST_SIZE` | +| Option | Environment Variable | Description | +|----------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | When true, the router calculates static cost for every operation. Required to use to access cost from custom modules. | +| `static_limit` | `SECURITY_COST_ANALYSIS_STATIC_LIMIT` | Maximum allowed cost. Operations exceeding this limit receive a 400 error. Set to 0 to calculate costs without enforcing a limit (useful for monitoring). | +| `list_size` | `SECURITY_COST_ANALYSIS_LIST_SIZE` | Default assumed size for list fields when no `@listSize` directive is specified. Should reflect typical list sizes in the API. | ## Customizing Cost with Directives @@ -118,9 +102,9 @@ but cost calculation can be fine-tuned using the `@cost` and `@listSize` directi ### The @cost Directive -`@cost` assigns custom weights to types, fields, or arguments that are more expensive than average. +`@cost` assigns custom weights to types, fields or arguments that are more expensive than average. -**On a type** — All fields returning this type inherit the weight: +When specified **on a type** — all fields returning this type inherit the weight: ```graphql type Address @cost(weight: 5) { @@ -130,7 +114,7 @@ type Address @cost(weight: 5) { } ``` -**On a field** — Override the cost for a specific field: +When specified **on a field** — only specific field has this weight: ```graphql type Query { @@ -138,7 +122,7 @@ type Query { } ``` -**On an argument** — Add cost for expensive argument processing: +When specified **on an argument** — add cost for expensive argument processing: ```graphql type Query { @@ -150,7 +134,7 @@ type Query { `@listSize` provides better list size estimates than the global default. -**Static size with `assumedSize`** — When a field always returns roughly the same number of items: +**Static size with `assumedSize`** — when a field always returns roughly the same number of items: ```graphql type Query { @@ -159,7 +143,7 @@ type Query { } ``` -**Dynamic size with `slicingArguments`** — When the list size is controlled by a pagination argument: +**Dynamic size with `slicingArguments`** — when the list size is controlled by a pagination argument: ```graphql type Query { @@ -209,7 +193,7 @@ This enables use cases like: - Custom rate limiting based on query cost - Logging expensive operations for analysis - Billing based on query complexity -- Dynamic throttling during high load +- Dynamic throttling during high-load peaks ## Error Responses @@ -219,33 +203,12 @@ When a query exceeds the static limit, the router returns a 400 status with an e { "errors": [ { - "message": "operation cost 1500 exceeds the maximum allowed static cost of 1000" + "message": "The estimated query cost 1540 exceeds the maximum allowed static cost 1500" } ] } ``` -## Differences from Apollo Demand Control - -For those familiar with Apollo Router's demand control feature, here are the key differences: - -| Aspect | Apollo | Cosmo | -|--------|--------|-------| -| Configuration location | `demand_control` | `security.cost_analysis` | -| Limit setting | `strategy.static_estimated.max` | `static_limit` | -| Measure-only mode | `mode: measure` | Set `static_limit: 0` with `enabled: true` | - -### Features Not Yet Implemented - -The following features from Apollo's demand control are not yet available: - -- **Actual cost measurement** — Measuring real cost after execution based on actual list sizes returned by subgraphs -- **Cost delta** — The difference between estimated and actual cost -- **Cost telemetry** — OTEL metrics and span attributes for cost values (`cost.estimated`, `cost.actual`, `cost.delta`) -- **`sizedFields` in @listSize** — Applying list size estimates to specific child fields rather than all list children -- **Operation type base costs** — Extra base cost for mutations (Apollo adds 10 for mutations) -- **Response header exposure** — Exposing cost values in response headers via Rhai scripts - ## Best Practices ### Start with Monitoring @@ -261,10 +224,10 @@ security: list_size: 10 ``` -### Choose list_size Carefully +### Pick Parameters for @list_size Carefully The default list size significantly impacts cost calculations. -If lists typically return 5-20 items, a default of 10 is reasonable. +If lists typically return 5–20 items, a default of 10 is reasonable. For fields that return large lists (100+ items), consider: 1. Using `@listSize(assumedSize: N)` on those specific fields @@ -325,3 +288,13 @@ query { With default list size of 10, this query has a cost of over 10,000. `@listSize` should be used to provide realistic estimates for deeply nested structures. + +## Features Not Yet Implemented + +- **Runtime or dynamic cost measurement** after execution based on actual list sizes returned by subgraphs +- **Cost telemetry** — OTEL metrics and span attributes for cost values (`cost.estimated`, `cost.actual`, `cost.delta`) +- `sizedFields` in @listSize for applying list size estimates to specific child fields rather than all children +- `requireOneSlicingArgument` for validation of queries +- **Input object fields** when used as parameters of fields +- **Arguments of directives** are not accounted for weights + From e6d7feeefd34b27773f9f5f492b4b646ab9f51f3 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:25:26 +0200 Subject: [PATCH 04/20] better wording --- docs/router/security/cost-control.mdx | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 3460bb06..49e35e51 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -11,26 +11,29 @@ thousands of resolver calls and overwhelm subgraphs, while a simple field lookup Cost analysis allows assigning weights to fields and estimating query complexity *before* execution begins. When cost analysis is enabled, the router calculates an estimated cost for each incoming operation based -on the fields requested, their configured weights, and expected list sizes. +on the fields requested, their configured weights and expected list sizes. Operations exceeding the configured limit are rejected immediately — no subgraph requests are made, protecting the infrastructure from resource exhaustion. -This feature implements the [IBM GraphQL Cost Directive Specification](https://ibm.github.io/graphql-specs/cost-spec.html), +Cosmo router implements the [IBM GraphQL Cost Directive Specification](https://ibm.github.io/graphql-specs/cost-spec.html), adapted for GraphQL Federation. ### Key differences from the IBM specification -Specification does not take into account the federation aspect. Some implementation details to -explain how we calculate static costs. Static cost value is based on the query plan. Query plan -use a federation of subgraphs to create a chain of fetches. This chain of fetches is invisible -(on the supergraph level) and could happen because of entity calls, `@requires` fetches, etc. -We account for those because they all make some type of queries more expensive. +Weights are plain integers instead of stringified floats as defined in the IBM spec. -We do not use stringified floats for weights but rather plan integer values. +The IBM specification does not account for federation. Static cost is calculated based on the query plan, +which uses a federation of subgraphs to create a chain of fetches. This chain is invisible at the supergraph +level and may include entity calls, `@requires` fetches, etc. These are accounted for because they make +certain queries more expensive. -Router supports calculation of static (estimated) costs only at this moment. -Dynamic (runtime) cost calculated on the actual data returned from the subgraphs, is in active -development. +When a field returns a list of objects with a type weight, the IBM spec does not multiply the type's own weight +by the list size — only the children's costs are multiplied. Our implementation multiplies both the type weight +and children's costs by the list size, resulting in higher cost estimates for list fields with weighted types. +This is done because federation may trigger entity fetches between subgraphs. + +Currently, only static (estimated) costs are supported. +Dynamic (runtime) cost, calculated from actual data returned by subgraphs, is in active development. ## How Cost is Calculated From 4e0fa211913a2c01db94f6df15ebf8e12552276c Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:29:56 +0200 Subject: [PATCH 05/20] do second pass of wording out --- docs/router/security/cost-control.mdx | 32 ++++++++++++--------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 49e35e51..3f4b0a89 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -37,8 +37,7 @@ Dynamic (runtime) cost, calculated from actual data returned by subgraphs, is in ## How Cost is Calculated -The router walks through the query and sums up the cost of each field. This is a simplified abstraction, -but this should be used as a basis to estimate how expensive queries are. +The router walks through the query and sums the cost of each field. By default, object types (including interfaces and unions) cost `1`, scalar and enum fields cost `0`. For example, this query has a cost of `4`: @@ -58,9 +57,8 @@ query { } } ``` -In reality, router takes into account the possible weights assigned to the same field coordinate -in different subgraphs. -It is possible to make particular field resolvers on particular subgraphs more expensive. +The router also accounts for weights assigned to the same field coordinate in different subgraphs, +allowing specific resolvers to be weighted differently per subgraph. ### List Fields Multiply Cost @@ -94,14 +92,13 @@ security: | Option | Environment Variable | Description | |----------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | When true, the router calculates static cost for every operation. Required to use to access cost from custom modules. | +| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | When true, the router calculates static cost for every operation. Required to access cost from custom modules. | | `static_limit` | `SECURITY_COST_ANALYSIS_STATIC_LIMIT` | Maximum allowed cost. Operations exceeding this limit receive a 400 error. Set to 0 to calculate costs without enforcing a limit (useful for monitoring). | | `list_size` | `SECURITY_COST_ANALYSIS_LIST_SIZE` | Default assumed size for list fields when no `@listSize` directive is specified. Should reflect typical list sizes in the API. | ## Customizing Cost with Directives -Default weights work for many APIs, -but cost calculation can be fine-tuned using the `@cost` and `@listSize` directives in the schema. +Default weights work for many APIs, but the `@cost` and `@listSize` directives allow fine-tuning. ### The @cost Directive @@ -117,7 +114,7 @@ type Address @cost(weight: 5) { } ``` -When specified **on a field** — only specific field has this weight: +When specified **on a field** — only that field carries the weight: ```graphql type Query { @@ -125,7 +122,7 @@ type Query { } ``` -When specified **on an argument** — add cost for expensive argument processing: +When specified **on an argument** — adds cost for expensive argument processing: ```graphql type Query { @@ -191,7 +188,7 @@ func (m *MyModule) Middleware(ctx core.RequestContext, next http.Handler) { } ``` -This enables use cases like: +Use cases: - Custom rate limiting based on query cost - Logging expensive operations for analysis @@ -216,8 +213,8 @@ When a query exceeds the static limit, the router returns a 400 status with an e ### Start with Monitoring -Start with `static_limit: 0` to calculate costs without rejecting any queries. -This allows traffic patterns to be understood before setting limits. +Start with `static_limit: 0` to calculate costs without rejecting queries. +This reveals traffic patterns before setting limits. ```yaml security: @@ -238,7 +235,7 @@ For fields that return large lists (100+ items), consider: ### Annotate Expensive Resolvers -`@cost` should be applied to fields that trigger expensive operations: +Apply `@cost` to fields that trigger expensive operations: - External API calls - Complex computations @@ -257,8 +254,7 @@ type Query { ### Design for Pagination -APIs with pagination provide better cost estimation. -`slicingArguments` gives the router accurate multipliers based on what clients actually request: +Pagination improves cost accuracy. `slicingArguments` provides precise multipliers based on client requests: ```graphql type Query { @@ -289,8 +285,8 @@ query { } ``` -With default list size of 10, this query has a cost of over 10,000. -`@listSize` should be used to provide realistic estimates for deeply nested structures. +With default list size of 10, this query costs over 10,000. +Use `@listSize` to provide realistic estimates for deeply nested structures. ## Features Not Yet Implemented From 73a9fa078b287bd51603575db080777bdf7cd2d1 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:51:34 +0200 Subject: [PATCH 06/20] add default values --- docs/router/security/cost-control.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 3f4b0a89..272e183b 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -90,11 +90,11 @@ security: list_size: 10 ``` -| Option | Environment Variable | Description | -|----------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | When true, the router calculates static cost for every operation. Required to access cost from custom modules. | -| `static_limit` | `SECURITY_COST_ANALYSIS_STATIC_LIMIT` | Maximum allowed cost. Operations exceeding this limit receive a 400 error. Set to 0 to calculate costs without enforcing a limit (useful for monitoring). | -| `list_size` | `SECURITY_COST_ANALYSIS_LIST_SIZE` | Default assumed size for list fields when no `@listSize` directive is specified. Should reflect typical list sizes in the API. | +| Option | Environment Variable | Default | Description | +|----------------|---------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------| +| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | `false` | When true, the router calculates static cost for every operation. Required to access cost from custom modules. | +| `static_limit` | `SECURITY_COST_ANALYSIS_STATIC_LIMIT` | `0` | Maximum allowed cost. Operations exceeding this limit receive a 400 error. 0 disables enforcement. | +| `list_size` | `SECURITY_COST_ANALYSIS_LIST_SIZE` | `10` | Default assumed size for list fields when no `@listSize` directive is specified. | ## Customizing Cost with Directives @@ -285,7 +285,7 @@ query { } ``` -With default list size of 10, this query costs over 10,000. +With a default list size of 10, this query costs over 10,000. Use `@listSize` to provide realistic estimates for deeply nested structures. ## Features Not Yet Implemented From c030a62a566b551d5e840cfaa260b65f9830170f Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:46:39 +0200 Subject: [PATCH 07/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 272e183b..355d52f2 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -290,7 +290,7 @@ Use `@listSize` to provide realistic estimates for deeply nested structures. ## Features Not Yet Implemented -- **Runtime or dynamic cost measurement** after execution based on actual list sizes returned by subgraphs +- **Runtime (dynamic) cost measurement** based on actual list sizes returned by subgraphs - **Cost telemetry** — OTEL metrics and span attributes for cost values (`cost.estimated`, `cost.actual`, `cost.delta`) - `sizedFields` in @listSize for applying list size estimates to specific child fields rather than all children - `requireOneSlicingArgument` for validation of queries From f45d0ae738531dd8bbfefea9c5801985fb027065 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:46:53 +0200 Subject: [PATCH 08/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 355d52f2..1a2a9054 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -190,7 +190,7 @@ func (m *MyModule) Middleware(ctx core.RequestContext, next http.Handler) { Use cases: -- Custom rate limiting based on query cost +- Apply rate limiting based on query cost - Logging expensive operations for analysis - Billing based on query complexity - Dynamic throttling during high-load peaks From bb2d9e816e5fbe7aa26f97b12c5a027afbcfe56f Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:47:14 +0200 Subject: [PATCH 09/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 1a2a9054..e9477910 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -191,7 +191,7 @@ func (m *MyModule) Middleware(ctx core.RequestContext, next http.Handler) { Use cases: - Apply rate limiting based on query cost -- Logging expensive operations for analysis +- Log expensive operations for analysis - Billing based on query complexity - Dynamic throttling during high-load peaks From 41b18554d1a795c5d2e2287130ef9a8e16ee2097 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:47:24 +0200 Subject: [PATCH 10/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index e9477910..1bf2df1e 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -192,7 +192,7 @@ Use cases: - Apply rate limiting based on query cost - Log expensive operations for analysis -- Billing based on query complexity +- Support billing based on query complexity - Dynamic throttling during high-load peaks ## Error Responses From 7117aad9632bb177e9ef73be78f4c524eb53cd48 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:47:42 +0200 Subject: [PATCH 11/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 1bf2df1e..736e50fa 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -193,7 +193,7 @@ Use cases: - Apply rate limiting based on query cost - Log expensive operations for analysis - Support billing based on query complexity -- Dynamic throttling during high-load peaks +- Throttle requests during high-load periods ## Error Responses From 7a430c8d1edf33ad7364d7cd33f612ad67552875 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:48:15 +0200 Subject: [PATCH 12/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 736e50fa..98d2820b 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -63,7 +63,7 @@ allowing specific resolvers to be weighted differently per subgraph. ### List Fields Multiply Cost When a field returns a list, the cost of that field and all its children is multiplied by the expected list size. -Since the router doesn't know actual list sizes at planning time, it uses estimates. +Since the router cannot determine actual list sizes during planning, it uses estimates. ```graphql query { From f06d347510b3636032f7e14bd808627c289cafab Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:48:42 +0200 Subject: [PATCH 13/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 98d2820b..64efbe9a 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -134,7 +134,7 @@ type Query { `@listSize` provides better list size estimates than the global default. -**Static size with `assumedSize`** — when a field always returns roughly the same number of items: +**Static size with `assumedSize`** — when a field consistently returns a predictable number of items: ```graphql type Query { From a7da02e1be9436771f9a29457bf9bc34dabb9d27 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:48:58 +0200 Subject: [PATCH 14/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 64efbe9a..7a7b73f9 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -28,7 +28,7 @@ level and may include entity calls, `@requires` fetches, etc. These are accounte certain queries more expensive. When a field returns a list of objects with a type weight, the IBM spec does not multiply the type's own weight -by the list size — only the children's costs are multiplied. Our implementation multiplies both the type weight +by the list size — only the children's costs are multiplied. This implementation multiplies both the type weight and children's costs by the list size, resulting in higher cost estimates for list fields with weighted types. This is done because federation may trigger entity fetches between subgraphs. From 3f6b2f0cf48c51656ead287c3c1e802337272a4c Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:49:37 +0200 Subject: [PATCH 15/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 7a7b73f9..e6e8a038 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -12,7 +12,7 @@ Cost analysis allows assigning weights to fields and estimating query complexity When cost analysis is enabled, the router calculates an estimated cost for each incoming operation based on the fields requested, their configured weights and expected list sizes. -Operations exceeding the configured limit are rejected immediately — no subgraph requests are made, +Operations exceeding the configured limit are rejected immediately. No subgraph requests are made, protecting the infrastructure from resource exhaustion. Cosmo router implements the [IBM GraphQL Cost Directive Specification](https://ibm.github.io/graphql-specs/cost-spec.html), From d2c725ecedd0da086abf439b691707ce79fca4b2 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:49:53 +0200 Subject: [PATCH 16/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index e6e8a038..cfcd553e 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -15,7 +15,7 @@ on the fields requested, their configured weights and expected list sizes. Operations exceeding the configured limit are rejected immediately. No subgraph requests are made, protecting the infrastructure from resource exhaustion. -Cosmo router implements the [IBM GraphQL Cost Directive Specification](https://ibm.github.io/graphql-specs/cost-spec.html), +The Cosmo Router implements the [IBM GraphQL Cost Directive Specification](https://ibm.github.io/graphql-specs/cost-spec.html), adapted for GraphQL Federation. ### Key differences from the IBM specification From 3be02d3f6da7064d5a824b86b5629917025969a6 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:50:05 +0200 Subject: [PATCH 17/20] Update docs/router/security/cost-control.mdx Co-authored-by: Brendan --- docs/router/security/cost-control.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index cfcd553e..8b5e413c 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -20,7 +20,7 @@ adapted for GraphQL Federation. ### Key differences from the IBM specification -Weights are plain integers instead of stringified floats as defined in the IBM spec. +Weights use plain integers instead of stringified floats as defined in the IBM spec. The IBM specification does not account for federation. Static cost is calculated based on the query plan, which uses a federation of subgraphs to create a chain of fetches. This chain is invisible at the supergraph From 0cda6b2578de54104025066a66f1ace16a39cfbe Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 10:52:34 +0200 Subject: [PATCH 18/20] fix the intro --- docs/router/security/cost-control.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 8b5e413c..dfffd043 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -6,7 +6,8 @@ icon: scale-balanced ## Overview -Some GraphQL queries are more expensive than others. A query requesting deeply nested lists can generate +Cost analysis prevents a single GraphQL request from using too many system resources and +slowing everything else down. A query requesting deeply nested lists can generate thousands of resolver calls and overwhelm subgraphs, while a simple field lookup costs almost nothing. Cost analysis allows assigning weights to fields and estimating query complexity *before* execution begins. From fbf447f692117c588dc6ea0ec1d2538f6159feb0 Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:46:22 +0200 Subject: [PATCH 19/20] use cost struct --- docs/router/security/cost-control.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index dfffd043..2d6a3627 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -174,15 +174,15 @@ In custom modules, the calculated cost is accessible through the operation conte ```go func (m *MyModule) Middleware(ctx core.RequestContext, next http.Handler) { - // Get the static cost after planning - cost, err := ctx.Operation().StaticCost() + // Get cost after planning + cost, err := ctx.Operation().Cost() if err != nil { // Cost analysis not enabled or plan not ready } - // Use cost for custom logic (rate limiting, logging, etc.) - if cost > m.warningThreshold { - m.logger.Warn("High cost operation", "cost", cost) + // Use estimated cost for custom logic (rate limiting, logging, etc.) + if cost.Estimated > m.warningThreshold { + m.logger.Warn("High cost operation", "cost", cost.Estimated) } next.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) From 2fb82a0bc08356d3f831b21f813f67ad7fd91add Mon Sep 17 00:00:00 2001 From: Yury Smolski <140245+ysmolski@users.noreply.github.com> Date: Fri, 30 Jan 2026 13:19:18 +0200 Subject: [PATCH 20/20] rename config vars --- docs/router/security/cost-control.mdx | 28 ++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/router/security/cost-control.mdx b/docs/router/security/cost-control.mdx index 2d6a3627..c7fb7056 100644 --- a/docs/router/security/cost-control.mdx +++ b/docs/router/security/cost-control.mdx @@ -87,15 +87,17 @@ Cost analysis is enabled in the router configuration: security: cost_analysis: enabled: true - static_limit: 1000 - list_size: 10 + mode: enforce + estimated_limit: 1000 + estimated_list_size: 10 ``` -| Option | Environment Variable | Default | Description | -|----------------|---------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------| -| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | `false` | When true, the router calculates static cost for every operation. Required to access cost from custom modules. | -| `static_limit` | `SECURITY_COST_ANALYSIS_STATIC_LIMIT` | `0` | Maximum allowed cost. Operations exceeding this limit receive a 400 error. 0 disables enforcement. | -| `list_size` | `SECURITY_COST_ANALYSIS_LIST_SIZE` | `10` | Default assumed size for list fields when no `@listSize` directive is specified. | +| Option | Environment Variable | Default | Description | +|-----------------------|----------------------------------------------|-----------|-----------------------------------------------------------------------------------------------| +| `enabled` | `SECURITY_COST_ANALYSIS_ENABLED` | `false` | When true, the router calculates costs for every operation. | +| `mode` | `SECURITY_COST_ANALYSIS_MODE` | `measure` | `measure` calculates costs only; `enforce` rejects operations exceeding the estimated limit. | +| `estimated_limit` | `SECURITY_COST_ANALYSIS_ESTIMATED_LIMIT` | `0` | Maximum allowed estimated cost. Only enforced when mode is `enforce`. 0 disables enforcement. | +| `estimated_list_size` | `SECURITY_COST_ANALYSIS_ESTIMATED_LIST_SIZE` | `10` | Default assumed size for list fields when no `@listSize` directive is specified. | ## Customizing Cost with Directives @@ -198,13 +200,13 @@ Use cases: ## Error Responses -When a query exceeds the static limit, the router returns a 400 status with an error: +When a query exceeds the estimated cost limit (in enforce mode), the router returns a 400 status: ```json { "errors": [ { - "message": "The estimated query cost 1540 exceeds the maximum allowed static cost 1500" + "message": "The estimated query cost 1540 exceeds the maximum allowed estimated cost 1500" } ] } @@ -212,16 +214,16 @@ When a query exceeds the static limit, the router returns a 400 status with an e ## Best Practices -### Start with Monitoring +### Start with Measure Mode -Start with `static_limit: 0` to calculate costs without rejecting queries. -This reveals traffic patterns before setting limits. +Start with `mode: measure` to calculate costs without rejecting queries. +This reveals traffic patterns before enabling enforcement. ```yaml security: cost_analysis: enabled: true - static_limit: 0 # Monitor only + mode: measure list_size: 10 ```