Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .changeset/safe-graphql-body-limit-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@croco/problems-core": patch
"@croco/transports-graphql": patch
---

Reject unsafe GraphQL request body limits during server initialization with the stable `transports-graphql/body-limit-invalid-configuration` Problem, while preserving an inclusive byte boundary for buffered and streamed requests.
42 changes: 36 additions & 6 deletions docs/problem-code-registry.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "croco.problem-code-registry.v1",
"problemCount": 582,
"problemCount": 583,
"problems": [
{
"code": "ACCESS_DENIED",
Expand Down Expand Up @@ -15217,6 +15217,36 @@
}
]
},
{
"code": "transports-graphql/body-limit-invalid-configuration",
"category": "InternalServerError",
"status": 500,
"title": "Internal Server Error",
"cookbookPath": "/reference/problem-recovery-cookbook/#transports-graphql-body-limit-invalid-configuration",
"recovery": {
"cause": "The GraphQL server was configured with an invalid request body byte boundary.",
"userAction": "Ask the operator to correct the service configuration before retrying.",
"operatorAction": "Set maxBodySizeBytes to a finite positive safe integer and restart the service.",
"retryability": "not-retryable",
"redactionPolicy": "operator-only",
"telemetry": {
"eventName": "croco.problem.error",
"severity": "error",
"attributes": ["problem.code", "problem.category", "problem.status"]
}
},
"lifecycle": {
"status": "active"
},
"sources": [
{
"file": "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
"line": 8,
"column": 5,
"kind": "problem-constructor"
}
]
},
{
"code": "transports-graphql/request-body-aborted",
"category": "BadRequest",
Expand All @@ -15241,7 +15271,7 @@
"sources": [
{
"file": "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
"line": 47,
"line": 63,
"column": 3,
"kind": "problem-class"
}
Expand Down Expand Up @@ -15271,7 +15301,7 @@
"sources": [
{
"file": "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
"line": 33,
"line": 49,
"column": 5,
"kind": "problem-constructor"
}
Expand Down Expand Up @@ -15301,7 +15331,7 @@
"sources": [
{
"file": "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
"line": 4,
"line": 20,
"column": 3,
"kind": "problem-class"
}
Expand Down Expand Up @@ -15331,7 +15361,7 @@
"sources": [
{
"file": "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
"line": 16,
"line": 32,
"column": 3,
"kind": "problem-class"
}
Expand Down Expand Up @@ -15361,7 +15391,7 @@
"sources": [
{
"file": "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
"line": 24,
"line": 40,
"column": 3,
"kind": "problem-class"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Generated Croco Problem code registry with recovery and telemetry m

> Generated by `pnpm problem-registry:write`. Do not edit this file by hand.

This cookbook documents 582 public Croco Problem codes. The deterministic JSON registry is generated at `docs/problem-code-registry.json`, and generated client union types are emitted at `packages/problems-core/src/generated/problem-code-registry.ts`.
This cookbook documents 583 public Croco Problem codes. The deterministic JSON registry is generated at `docs/problem-code-registry.json`, and generated client union types are emitted at `packages/problems-core/src/generated/problem-code-registry.ts`.

## Index

Expand Down Expand Up @@ -520,6 +520,7 @@ This cookbook documents 582 public Croco Problem codes. The deterministic JSON r
| [`testing/transaction-context-not-active`](#testing-transaction-context-not-active) | InternalServerError | 500 | conditional | operator-only | active | 1 |
| [`TOKEN_LIMIT_EXCEEDED`](#token-limit-exceeded) | BadRequest | 400 | not-retryable | public | active | 1 |
| [`TOOL_EXECUTION_ERROR`](#tool-execution-error) | InternalServerError | 500 | conditional | operator-only | active | 1 |
| [`transports-graphql/body-limit-invalid-configuration`](#transports-graphql-body-limit-invalid-configuration) | InternalServerError | 500 | not-retryable | operator-only | active | 1 |
| [`transports-graphql/request-body-aborted`](#transports-graphql-request-body-aborted) | BadRequest | 400 | not-retryable | public | active | 1 |
| [`transports-graphql/request-body-too-large`](#transports-graphql-request-body-too-large) | PayloadTooLarge | 413 | not-retryable | public | active | 1 |
| [`transports-graphql/resolvers-not-configured`](#transports-graphql-resolvers-not-configured) | InternalServerError | 500 | conditional | operator-only | active | 1 |
Expand Down Expand Up @@ -9730,6 +9731,24 @@ Sources:

- `packages/llm-core/src/libs/problems/LlmServiceProblem.ts:61:5` (problem-constructor)

<a id="transports-graphql-body-limit-invalid-configuration"></a>

## `transports-graphql/body-limit-invalid-configuration`

- Category: `InternalServerError`
- HTTP status: `500` Internal Server Error
- Retryability: `not-retryable`
- Redaction policy: `operator-only`
- Lifecycle: `active`
- Cause: The GraphQL server was configured with an invalid request body byte boundary.
- User action: Ask the operator to correct the service configuration before retrying.
- Operator action: Set maxBodySizeBytes to a finite positive safe integer and restart the service.
- Telemetry: `croco.problem.error` (error) with `problem.code`, `problem.category`, `problem.status`

Sources:

- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:8:5` (problem-constructor)

<a id="transports-graphql-request-body-aborted"></a>

## `transports-graphql/request-body-aborted`
Expand All @@ -9746,7 +9765,7 @@ Sources:

Sources:

- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:47:3` (problem-class)
- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:63:3` (problem-class)

<a id="transports-graphql-request-body-too-large"></a>

Expand All @@ -9764,7 +9783,7 @@ Sources:

Sources:

- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:33:5` (problem-constructor)
- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:49:5` (problem-constructor)

<a id="transports-graphql-resolvers-not-configured"></a>

Expand All @@ -9782,7 +9801,7 @@ Sources:

Sources:

- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:4:3` (problem-class)
- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:20:3` (problem-class)

<a id="transports-graphql-schema-not-configured"></a>

Expand All @@ -9800,7 +9819,7 @@ Sources:

Sources:

- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:16:3` (problem-class)
- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:32:3` (problem-class)

<a id="transports-graphql-server-not-initialized"></a>

Expand All @@ -9818,7 +9837,7 @@ Sources:

Sources:

- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:24:3` (problem-class)
- `packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts:40:3` (problem-class)

<a id="transports-http-body-limit-invalid-configuration"></a>

Expand Down
44 changes: 38 additions & 6 deletions packages/problems-core/src/generated/problem-code-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ProblemCodeRegistry } from "../libs/ProblemRegistry";

export const CROCO_PROBLEM_CODE_REGISTRY = {
version: "croco.problem-code-registry.v1",
problemCount: 582,
problemCount: 583,
problems: [
{
code: "ACCESS_DENIED",
Expand Down Expand Up @@ -15873,6 +15873,38 @@ export const CROCO_PROBLEM_CODE_REGISTRY = {
},
],
},
{
code: "transports-graphql/body-limit-invalid-configuration",
category: "InternalServerError",
status: 500,
title: "Internal Server Error",
cookbookPath:
"/reference/problem-recovery-cookbook/#transports-graphql-body-limit-invalid-configuration",
recovery: {
cause: "The GraphQL server was configured with an invalid request body byte boundary.",
userAction: "Ask the operator to correct the service configuration before retrying.",
operatorAction:
"Set maxBodySizeBytes to a finite positive safe integer and restart the service.",
retryability: "not-retryable",
redactionPolicy: "operator-only",
telemetry: {
eventName: "croco.problem.error",
severity: "error",
attributes: ["problem.code", "problem.category", "problem.status"],
},
},
lifecycle: {
status: "active",
},
sources: [
{
file: "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
line: 8,
column: 5,
kind: "problem-constructor",
},
],
},
{
code: "transports-graphql/request-body-aborted",
category: "BadRequest",
Expand All @@ -15898,7 +15930,7 @@ export const CROCO_PROBLEM_CODE_REGISTRY = {
sources: [
{
file: "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
line: 47,
line: 63,
column: 3,
kind: "problem-class",
},
Expand Down Expand Up @@ -15930,7 +15962,7 @@ export const CROCO_PROBLEM_CODE_REGISTRY = {
sources: [
{
file: "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
line: 33,
line: 49,
column: 5,
kind: "problem-constructor",
},
Expand Down Expand Up @@ -15963,7 +15995,7 @@ export const CROCO_PROBLEM_CODE_REGISTRY = {
sources: [
{
file: "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
line: 4,
line: 20,
column: 3,
kind: "problem-class",
},
Expand Down Expand Up @@ -15996,7 +16028,7 @@ export const CROCO_PROBLEM_CODE_REGISTRY = {
sources: [
{
file: "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
line: 16,
line: 32,
column: 3,
kind: "problem-class",
},
Expand Down Expand Up @@ -16029,7 +16061,7 @@ export const CROCO_PROBLEM_CODE_REGISTRY = {
sources: [
{
file: "packages/transports-graphql/src/libs/problems/GraphQLTransportProblems.ts",
line: 24,
line: 40,
column: 3,
kind: "problem-class",
},
Expand Down
6 changes: 6 additions & 0 deletions packages/transports-graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ const schema = await new SchemaCompiler().compile({ resolvers: [HealthResolver]
const server = new GraphQLServer({ schema });
```

`maxBodySizeBytes` defaults to 1 MiB and must be a finite positive safe integer. The
server validates this option during initialization, before opening its listener. Both
declared `Content-Length` values and actual streamed bytes use an inclusive boundary:
a request with exactly the configured number of bytes is accepted, while the next byte
is rejected with `transports-graphql/request-body-too-large`.

Schemas compiled through `SchemaCompiler` execute the `UseGuards`, `Roles`, and
`UseInterceptors` declarations recorded by `@croco/protocols-graphql`. Request
headers are available as `context.headers`, custom server context is preserved, and
Expand Down
1 change: 1 addition & 0 deletions packages/transports-graphql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/** GraphQL Yoga server runtime. */
export { GraphQLServer } from "./libs/GraphQLServer";
export {
GraphQLBodyLimitConfigurationProblem,
GraphQLRequestBodyAbortedProblem,
GraphQLRequestBodyTooLargeProblem,
GraphQLResolversNotConfiguredProblem,
Expand Down
13 changes: 12 additions & 1 deletion packages/transports-graphql/src/libs/GraphQLServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Problem } from "@croco/problems-core";
import { isProblem, problemToGraphQLError } from "@croco/protocols-graphql";
import { createYoga, maskError } from "graphql-yoga";
import {
GraphQLBodyLimitConfigurationProblem,
GraphQLRequestBodyAbortedProblem,
GraphQLRequestBodyTooLargeProblem,
GraphQLSchemaNotConfiguredProblem,
Expand Down Expand Up @@ -38,12 +39,22 @@ export class GraphQLServer {
private yogaHandler: YogaHandler | null = null;
private server: Server | null = null;
private initialized = false;
private maxBodySizeBytes = DEFAULT_MAX_BODY_SIZE_BYTES;

constructor(private options: GraphQLServerOptions = {}) {}

async initialize(): Promise<void> {
if (this.initialized) return;

const maxBodySizeBytes =
this.options.maxBodySizeBytes === undefined
? DEFAULT_MAX_BODY_SIZE_BYTES
: this.options.maxBodySizeBytes;
if (!Number.isSafeInteger(maxBodySizeBytes) || maxBodySizeBytes <= 0) {
throw new GraphQLBodyLimitConfigurationProblem();
}
this.maxBodySizeBytes = maxBodySizeBytes;

const {
schema,
schemaOptions,
Expand Down Expand Up @@ -181,7 +192,7 @@ export class GraphQLServer {

private getBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
const maxBodySizeBytes = this.options.maxBodySizeBytes ?? DEFAULT_MAX_BODY_SIZE_BYTES;
const maxBodySizeBytes = this.maxBodySizeBytes;
const contentLength = req.headers["content-length"];

if (typeof contentLength === "string") {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { Problem, ProblemCategory } from "@croco/problems-core";

const PROBLEM_TYPE_BASE = "https://croco.dev/problems/transports-graphql";

/** The configured request body boundary cannot be enforced safely. */
export class GraphQLBodyLimitConfigurationProblem extends Problem {
constructor() {
super(
"transports-graphql/body-limit-invalid-configuration",
ProblemCategory.InternalServerError,
"maxBodySizeBytes must be a finite positive safe integer",
{
type: `${PROBLEM_TYPE_BASE}/body-limit-invalid-configuration`,
},
);
}
}

export class GraphQLResolversNotConfiguredProblem extends Problem {
readonly code = "transports-graphql/resolvers-not-configured";
readonly category = ProblemCategory.InternalServerError;
Expand Down
Loading
Loading