@lenne.tech/nest-server 11.42.3 → 11.42.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/configurable-features.md +1 -0
- package/.claude/rules/package-management.md +1 -1
- package/.claude/rules/testing.md +4 -4
- package/CLAUDE.md +2 -1
- package/FRAMEWORK-API.md +2 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +8 -0
- package/dist/core/common/services/core-body-parser.initializer.d.ts +26 -0
- package/dist/core/common/services/core-body-parser.initializer.js +136 -0
- package/dist/core/common/services/core-body-parser.initializer.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth.module.js +7 -3
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +9 -0
- package/dist/core/modules/error-code/error-codes.js +8 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core.module.js +2 -0
- package/dist/core.module.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +28 -0
- package/docs/error-codes.md +16 -0
- package/docs/security-overrides.md +18 -2
- package/migration-guides/11.42.3-to-11.42.4.md +185 -0
- package/package.json +3 -3
- package/src/core/common/interfaces/server-options.interface.ts +89 -0
- package/src/core/common/services/core-body-parser.initializer.ts +232 -0
- package/src/core/modules/better-auth/core-better-auth.module.ts +24 -4
- package/src/core/modules/error-code/error-codes.ts +9 -0
- package/src/core.module.ts +4 -0
|
@@ -45,6 +45,7 @@ The `CoreModule` is a dynamic module that bootstraps the entire framework:
|
|
|
45
45
|
| **Central S3 (optional)** | `CoreS3Service` — globally provided **and exported** by `CoreModule`, inert unless `s3` is configured *and* names a `bucket` (a bucket-less block is ignored with a warning). Backs `file.storage: 's3'` and TUS staging (`tus.s3Staging`). Requires the OPTIONAL peers `@aws-sdk/client-s3` (+ `@aws-sdk/s3-request-presigner` for `presignedDownloads`) — configured but missing **fails the boot**. `@aws-sdk/lib-storage` (optional) uploads streams of unknown length in parts; without it they are buffered in memory, with one boot warning |
|
|
46
46
|
| **Configuration System** | `config.env.ts` with ENV variables, `NEST_SERVER_CONFIG` JSON, `NSC__*` prefixes |
|
|
47
47
|
| **Cookie Handling** | Enabled by default (`cookies: true`), configurable via `ICookiesConfig` with `exposeTokenInBody` option |
|
|
48
|
+
| **Body Parser Limits** | `bodyParser.json.limit` / `bodyParser.urlencoded.limit` (default 100 kB each, body-parser's own). Applied in place to the parsers NestJS registers, so they hold under `Test.createTestingModule()` too. Every 413 is answered with `#LTNS_0304` plus size and limit, and logged with the route and the config key — see [0d. Body parsers](#0d-body-parsers) |
|
|
48
49
|
| **Unified CORS** | Single `cors` config propagates to GraphQL, REST, and BetterAuth layers |
|
|
49
50
|
| **Dual Auth Modes** | IAM-Only (BetterAuth) or Legacy+IAM for migration periods |
|
|
50
51
|
|
|
@@ -326,6 +327,12 @@ The following diagram shows the exact order of execution from HTTP request to re
|
|
|
326
327
|
| - credentials: true when cookies enabled |
|
|
327
328
|
| - Origins from appUrl/baseUrl/cors.allowedOrigins |
|
|
328
329
|
| - Propagated to BetterAuth trustedOrigins |
|
|
330
|
+
| |
|
|
331
|
+
| 0d. Body parsers: json + urlencoded [always] |
|
|
332
|
+
| - Registered by NestJS in app.init(), limits from |
|
|
333
|
+
| bodyParser.*.limit (default 100 kB each) |
|
|
334
|
+
| - Over the limit: 413 #LTNS_0304 BEFORE auth and |
|
|
335
|
+
| routing, REST and GraphQL alike |
|
|
329
336
|
+----------------------------+----------------------------+
|
|
330
337
|
|
|
|
331
338
|
+----------------------------v----------------------------+
|
|
@@ -551,6 +558,27 @@ if (!isCorsDisabled(envConfig.cors)) {
|
|
|
551
558
|
|
|
552
559
|
> **Security:** steps 2 and 4 grant the derived origin credentialed CORS. If the apex domain is not trusted (e.g. a third-party-hosted marketing site whose XSS surface you do not control), set `cors.deriveAppUrl: false` and list the frontend origin explicitly via `appUrl` or `cors.allowedOrigins`. The derivation never yields a bare TLD (`https://api.dev` stays unchanged) and never emits the opaque `null` origin. With `cors.deriveAppUrl: false`, a host-split localhost `baseUrl` falls back to the `http://localhost:3001` default.
|
|
553
560
|
|
|
561
|
+
#### 0d. Body parsers
|
|
562
|
+
|
|
563
|
+
NestJS registers two global body parsers on every Express application — `application/json` and `application/x-www-form-urlencoded` (`extended: true`) — inside `app.init()`, i.e. after everything `main.ts` registers and before the NestJS middleware chain. GraphQL has no parser of its own: Apollo reads the body the global JSON parser produced, so one limit governs REST and GraphQL.
|
|
564
|
+
|
|
565
|
+
Both use body-parser's default limit of **100 kB** unless `bodyParser` says otherwise (since 11.42.4):
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
// config.env.ts — documents carry HTML: allow 2 MB of JSON, keep form posts at the default
|
|
569
|
+
bodyParser: { json: { limit: '2mb' } },
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
`CoreBodyParserInitializer` applies it during module init by replacing the handle of the global parser layers NestJS created — in place, because NestJS registers them before any init hook runs and a parser appended later would sit behind the routes. Running during module init is what makes the limit hold under `Test.createTestingModule()`, which never runs `main.ts`. A path-scoped parser (`server.use('/upload', json({ limit }))` in `main.ts`) runs before the global one and is left untouched — that remains the way to give one path a different limit.
|
|
573
|
+
|
|
574
|
+
The parser runs **before authentication and routing**. Over the limit, the whole request is refused:
|
|
575
|
+
|
|
576
|
+
```json
|
|
577
|
+
{ "statusCode": 413, "message": "#LTNS_0304: Request body too large [137270 bytes, limit 102400 bytes]", "error": "Payload Too Large" }
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
and the server logs a warning with method, path (query string stripped) and the config key to raise. Other parser errors (malformed JSON → 400) pass through unchanged. The default is deliberately not raised for everybody: `JSON.parse` is synchronous, and a larger pre-auth limit is a larger pre-auth event-loop block for every route, including unauthenticated ones.
|
|
581
|
+
|
|
554
582
|
### NestJS Middleware Chain (CoreModule)
|
|
555
583
|
|
|
556
584
|
NestJS middleware runs for every request after Express-level middleware. Registration happens in `CoreModule.configure()`:
|
package/docs/error-codes.md
CHANGED
|
@@ -262,6 +262,22 @@ All error codes follow the format: `#PREFIX_XXXX: Description`
|
|
|
262
262
|
|
|
263
263
|
---
|
|
264
264
|
|
|
265
|
+
### LTNS_0304: requestBodyTooLarge
|
|
266
|
+
|
|
267
|
+
**Message:** Request body too large
|
|
268
|
+
|
|
269
|
+
**Description:** Answered with HTTP 413 when a JSON or URL-encoded request body exceeds the body parser's limit. The parser runs before authentication and routing, so the whole request is rejected. The message carries the size and the limit in brackets, e.g. `#LTNS_0304: Request body too large [137270 bytes, limit 102400 bytes]` (the size is missing when the client sent no `Content-Length`). The server logs a warning with the route and the config key.
|
|
270
|
+
|
|
271
|
+
**Solution:** Send a smaller body (split the operation, or upload large content through the file endpoints). If payloads of this size are legitimate for the route, raise the limit via `bodyParser.json.limit` / `bodyParser.urlencoded.limit` in the server options.
|
|
272
|
+
|
|
273
|
+
**Parameters:** none (the size details are part of the developer message, not of the translation)
|
|
274
|
+
|
|
275
|
+
**Translations:**
|
|
276
|
+
- DE: Die gesendeten Daten sind zu groß und wurden nicht verarbeitet.
|
|
277
|
+
- EN: The submitted data is too large and was not processed.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
265
281
|
## Resource Errors (LTNS_0400-LTNS_0499)
|
|
266
282
|
|
|
267
283
|
### LTNS_0400: resourceNotFound
|
|
@@ -11,15 +11,23 @@ A green `pnpm audit` inside the framework repo says nothing about your tree.
|
|
|
11
11
|
|
|
12
12
|
## What this concretely means for you
|
|
13
13
|
|
|
14
|
-
The framework pulls in
|
|
15
|
-
**exact-pin
|
|
14
|
+
The framework pulls in four transitive packages that its `@nestjs/*` dependencies exact-pin, or
|
|
15
|
+
used to **exact-pin**, to a vulnerable version:
|
|
16
16
|
|
|
17
17
|
| Package | Advisory | Why it cannot resolve forward on its own |
|
|
18
18
|
|---------|----------|------------------------------------------|
|
|
19
19
|
| `ws` | [GHSA-96hv-2xvq-fx4p](https://github.com/advisories/GHSA-96hv-2xvq-fx4p) — high: memory-exhaustion DoS + uninitialized memory disclosure. Patched `>=8.21.0` | Older `@nestjs/graphql` releases declare `"ws": "8.20.1"` — an **exact pin**, not a caret. 13.4.5, the version this framework declares, pins `8.21.3` |
|
|
20
20
|
| `js-yaml` | [GHSA-pm4m-ph32-ghv5](https://github.com/advisories/GHSA-pm4m-ph32-ghv5) — high: exponential parsing time in flow collections (DoS), patched `>=5.2.2`; [GHSA-r3ph-w7gj-g6xm](https://github.com/advisories/GHSA-r3ph-w7gj-g6xm) — moderate: `maxTotalMergeKeys` does not bound CPU use for empty merge sources, covers `<=5.4.0`, patched `>=5.4.1` (published 2026-09-29) | `@nestjs/swagger` declares js-yaml as an **exact pin**, the same shape as the `ws` case: older releases `5.2.1`, 11.4.7 — the version this framework declares, and the newest 11.x — `5.3.0`. No swagger update reaches the fix, so **this entry is load-bearing again** |
|
|
21
|
+
| `@graphql-tools/utils` | [GHSA-7mx3-vvmw-hjmv](https://github.com/advisories/GHSA-7mx3-vvmw-hjmv) — high: prototype pollution in `mergeDeep`, which runs while the GraphQL schema is assembled. Covers `<=12.0.0`, patched `>=12.0.1` (added 2026-10-06) | `@nestjs/graphql` 13.4.5 — the version this framework declares, and the newest 13.x — declares `"@graphql-tools/utils": "12.0.0"`, an **exact pin**. Only `@nestjs/graphql` 14.x moves it, so **this entry is load-bearing**. `@apollo/server` reaches it too, through `@graphql-tools/schema` / `merge` — and in a lockfile that still holds schema 10.0.x (resolved before 2026-08-12), as **utils 11.x**: vulnerable too, with no 11.x fix, and outside the override key. Lift it with `pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge`; the current releases request utils `^12.0.3`, which needs Node >=22.15 |
|
|
21
22
|
| `multer` | [GHSA-wc9g-mqfw-jrwm](https://github.com/advisories/GHSA-wc9g-mqfw-jrwm), [GHSA-535w-7cp7-47q4](https://github.com/advisories/GHSA-535w-7cp7-47q4), [GHSA-qfvm-cv95-jqjf](https://github.com/advisories/GHSA-qfvm-cv95-jqjf) — high: DoS via crafted multipart input; [GHSA-qvfw-j98x-7q72](https://github.com/advisories/GHSA-qvfw-j98x-7q72) — low: file size limit bypass. Patched `>=2.3.0` | `@nestjs/platform-express` up to 11.2.5 declares `"multer": "2.2.0"` — an **exact pin**. A direct `multer` dependency does not move it: you get both copies, and FileInterceptor uses the vulnerable one. 11.2.6, which this framework declared from 11.41.4, pins `2.4.0`, and so does 11.2.7, declared since 11.41.7 |
|
|
22
23
|
|
|
24
|
+
**Status since 11.42.4:** `@graphql-tools/utils` joins `js-yaml` as load-bearing — no `@nestjs/graphql`
|
|
25
|
+
13.x resolves past the exact pin. The 11.42.4 release also covers `proxy-addr` (GHSA-jqcg-44mw-7w3h,
|
|
26
|
+
critical: IP spoofing through an IPv4-mapped IPv6 address in a trusted subnet — it is what
|
|
27
|
+
`trustProxy` hands to Express). That one needs **no override**: `express` requests `^2.0.7` and the
|
|
28
|
+
fix `2.0.8` is in range, but an existing lockfile keeps `2.0.7` until you refresh it:
|
|
29
|
+
`pnpm update --depth Infinity proxy-addr`.
|
|
30
|
+
|
|
23
31
|
**Status since 11.41.5:** `ws` and `multer` resolve to a patched version on their own with the
|
|
24
32
|
`@nestjs/*` versions this framework declares; their entries below are insurance for a project whose
|
|
25
33
|
own `package.json` pins an older `@nestjs/graphql` or `@nestjs/platform-express`. **`js-yaml` does
|
|
@@ -54,6 +62,14 @@ overrides:
|
|
|
54
62
|
# Keep the target in LOCKSTEP with the multer version @lenne.tech/nest-server declares (2.4.0 since 11.41.4).
|
|
55
63
|
# Inert on @nestjs/platform-express 11.2.6+, which pins 2.4.0 itself.
|
|
56
64
|
'multer@>=2.0.0 <2.4.0': '2.4.0'
|
|
65
|
+
|
|
66
|
+
# @nestjs/graphql 13.4.5 exact-pins @graphql-tools/utils@12.0.0 — GHSA-7mx3-vvmw-hjmv (high, patched >=12.0.1).
|
|
67
|
+
# Target 12.0.1 rather than the newest 12.x: 12.0.2+ requires Node >=22.15 via @whatwg-node/promise-helpers@2.
|
|
68
|
+
# If your engines floor is already >=22.15, prefer the newest 12.x, so the caret requesters are not held back.
|
|
69
|
+
# Does NOT reach a utils 11.x pulled in by an older @graphql-tools/schema 10.0.x — refresh that one instead:
|
|
70
|
+
# pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge (current releases need Node >=22.15)
|
|
71
|
+
# Remove once @nestjs/graphql pins >=12.0.1 in the major you use.
|
|
72
|
+
'@graphql-tools/utils@>=12.0.0 <12.0.1': '12.0.1'
|
|
57
73
|
```
|
|
58
74
|
|
|
59
75
|
### Retired: `@hono/node-server` (removed 2026-08-22, nest-server 11.36.1)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Migration Guide: 11.42.3 → 11.42.4
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
| Category | Details |
|
|
6
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| **New option** | `bodyParser.json.limit` / `bodyParser.urlencoded.limit` — the request body limit, configurable for the first time (§1) |
|
|
8
|
+
| **Improvement** | A request over the body limit is answered with `#LTNS_0304` plus size and limit, and logged with the route (§2) |
|
|
9
|
+
| **Security** | `proxy-addr` 2.0.8 (critical) and `@graphql-tools/utils` 12.0.1 (high) — your project needs a lockfile refresh and one override (§3) |
|
|
10
|
+
| **Bugfix** | Without `betterAuth.secret` and with JWT secrets shorter than 32 characters, native Better-Auth endpoints rejected the session right after sign-in (§4) |
|
|
11
|
+
| **Dependencies** | `nodemailer` 10.0.14 (patch: address-parser and DKIM header fixes) — no action needed |
|
|
12
|
+
| **Migration Effort** | Small — `pnpm update` plus one override in `pnpm-workspace.yaml` (§3). Projects that raise the body limit in `main.ts` can move it into the config (§1) |
|
|
13
|
+
|
|
14
|
+
## Quick Migration
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm update @lenne.tech/nest-server
|
|
18
|
+
pnpm update --depth Infinity proxy-addr
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then add the `@graphql-tools/utils` override from §3 to `pnpm-workspace.yaml`, run `pnpm install`,
|
|
22
|
+
and check §3 for an older `@graphql-tools/utils` 11.x copy, which the override does not reach.
|
|
23
|
+
Vendor-mode projects pick up the core update and need the same two dependency steps. The body limit
|
|
24
|
+
needs no configuration: unset, both parsers keep the 100 kB they had.
|
|
25
|
+
|
|
26
|
+
## 1. The request body limit is configurable
|
|
27
|
+
|
|
28
|
+
NestJS registers a JSON and a URL-encoded body parser on every application, and until now neither had
|
|
29
|
+
a limit anybody chose: body-parser's default of **100 kB** applied, and nothing in the framework or the
|
|
30
|
+
starter could change it. A request over it is refused in middleware — before authentication, before
|
|
31
|
+
routing, GraphQL included — so a document that grew past 100 kB could no longer be saved, and every
|
|
32
|
+
retry failed the same way.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// config.env.ts — documents carry HTML: allow 2 MB of JSON, keep form posts at the default
|
|
36
|
+
bodyParser: {
|
|
37
|
+
json: { limit: '2mb' },
|
|
38
|
+
},
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
| Value | Meaning |
|
|
42
|
+
| ---------------------- | -------------------------------------------------------------------- |
|
|
43
|
+
| unset | 100 kB, exactly as before |
|
|
44
|
+
| `2097152` | bytes |
|
|
45
|
+
| `'500kb'`, `'2mb'` | 1024-based size; units `b` / `kb` / `mb` / `gb`, case-insensitive |
|
|
46
|
+
| `0`, negative, `'lots'` | refused with an error log at boot; the parser keeps its limit |
|
|
47
|
+
|
|
48
|
+
The JSON and URL-encoded limits are separate on purpose: raising one does not raise the other.
|
|
49
|
+
|
|
50
|
+
**Raise it only where the data model needs it, and only as far.** The parser runs before
|
|
51
|
+
authentication, and `JSON.parse` blocks the event loop for the whole body — measured on a realistic
|
|
52
|
+
document shape, about 0.5 ms for 100 kB, 16 ms for 1 MB and 55 ms for 2 MB per request. That is why
|
|
53
|
+
the default stays at 100 kB.
|
|
54
|
+
|
|
55
|
+
**If you raise the limit in `main.ts` today**, move it into the config:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// main.ts — before: not applied in the e2e suite
|
|
59
|
+
server.useBodyParser('json', { limit: '2mb' });
|
|
60
|
+
|
|
61
|
+
// config.env.ts — after: applied in production AND under Test.createTestingModule()
|
|
62
|
+
bodyParser: { json: { limit: '2mb' } },
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The config is applied during module init, so it holds in every test that builds the app with
|
|
66
|
+
`Test.createTestingModule(...).createNestApplication()` — which never runs `main.ts`, so a
|
|
67
|
+
`useBodyParser()` call there never reached the e2e suite. Once configured, the value also replaces a
|
|
68
|
+
global parser your `main.ts` registered; keeping both is harmless, but the config wins.
|
|
69
|
+
|
|
70
|
+
A **path-scoped** parser is left alone: `server.use('/upload', json({ limit: '20mb' }))` in `main.ts`
|
|
71
|
+
still gives that one path its own limit. The parser runs before routing, so a per-route limit can only
|
|
72
|
+
key on the path — and all GraphQL operations share one.
|
|
73
|
+
|
|
74
|
+
**`rawBody: true`:** `req.rawBody` is kept for apps created with `NestFactory.create(…, { rawBody:
|
|
75
|
+
true })`. Under `Test.createTestingModule()` the options passed to `createNestApplication()` are not
|
|
76
|
+
visible to providers, so a test app created with `{ rawBody: true }` gets no `req.rawBody` once a limit
|
|
77
|
+
is configured.
|
|
78
|
+
|
|
79
|
+
## 2. An oversized request says what happened
|
|
80
|
+
|
|
81
|
+
Before, the caller received:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{ "statusCode": 413, "message": "request entity too large" }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
and the server logged the same text as an error with a stack trace. Now, configured or not:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{ "statusCode": 413, "message": "#LTNS_0304: Request body too large [137270 bytes, limit 102400 bytes]", "error": "Payload Too Large" }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The size is present when the client sent a `Content-Length`. Frontends using the error translation of
|
|
94
|
+
`@lenne.tech/nuxt-extensions` show the translated `LTNS_0304` text. The server logs one warning per
|
|
95
|
+
refused request, with method, path (without query string) and the config key to raise:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
413 PATCH /offers/6650…: json body of 137270 bytes exceeds the limit of 102400 bytes. If payloads of this size are legitimate here, raise `bodyParser.json.limit` in the server options.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The status code is unchanged. Only clients that compare the message text against
|
|
102
|
+
`'request entity too large'` need adjusting — branch on the status or on `LTNS_0304` instead.
|
|
103
|
+
|
|
104
|
+
## 3. Two dependency advisories your project must fix itself
|
|
105
|
+
|
|
106
|
+
`pnpm` overrides apply only to the project that runs the install, so the framework's own fixes do not
|
|
107
|
+
reach your tree — see [docs/security-overrides.md](../docs/security-overrides.md).
|
|
108
|
+
|
|
109
|
+
| Package | Advisory | What your project needs |
|
|
110
|
+
| ------- | -------- | ----------------------- |
|
|
111
|
+
| `proxy-addr` | [GHSA-jqcg-44mw-7w3h](https://github.com/advisories/GHSA-jqcg-44mw-7w3h) — **critical**: IP spoofing through an IPv4-mapped IPv6 address in a trusted subnet. Patched `>=2.0.8` | A lockfile refresh. `express` requests `^2.0.7`, so `2.0.8` is in range, but an existing lockfile stays on `2.0.7` |
|
|
112
|
+
| `@graphql-tools/utils` | [GHSA-7mx3-vvmw-hjmv](https://github.com/advisories/GHSA-7mx3-vvmw-hjmv) — high: prototype pollution in `mergeDeep` (schema assembly). Covers `<=12.0.0` — **11.x included, with no 11.x fix**. Patched `>=12.0.1` | An override for the copy `@nestjs/graphql` 13.4.5 exact-pins (`12.0.0`), and — in an older lockfile — a refresh of `@graphql-tools/schema` / `merge`, which can still pull in 11.x (see below) |
|
|
113
|
+
|
|
114
|
+
`proxy-addr` is what Express uses to compute `request.ip` from `X-Forwarded-For` as far as
|
|
115
|
+
`trustProxy` allows. With `trustProxy` set, every IP-keyed rate limit — the framework's own and any of
|
|
116
|
+
yours — depends on it.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pnpm update --depth Infinity proxy-addr
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
# pnpm-workspace.yaml
|
|
124
|
+
overrides:
|
|
125
|
+
# @nestjs/graphql 13.4.5 exact-pins @graphql-tools/utils@12.0.0 — GHSA-7mx3-vvmw-hjmv (high, patched >=12.0.1).
|
|
126
|
+
# 12.0.2+ requires Node >=22.15; take the newest 12.x instead if your engines floor allows it.
|
|
127
|
+
'@graphql-tools/utils@>=12.0.0 <12.0.1': '12.0.1'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Check for a second, older copy.** `@apollo/server` 5.x requests `@graphql-tools/schema` `^10.0.0`. A
|
|
131
|
+
lockfile resolved before schema 10.1.0 (published 2026-08-12) holds a 10.0.x, which requests utils
|
|
132
|
+
`^11.1.0` — vulnerable, outside the override key above, and with no fix in the 11.x line. Look for it:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
grep -n "'@graphql-tools/utils@11" pnpm-lock.yaml
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
If it is there, lift the requesters into their current lines, which ask for utils 12:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The current releases (schema 10.1.3, merge 9.2.6) request utils `^12.0.3`, which depends on
|
|
145
|
+
`@whatwg-node/promise-helpers` 2 and therefore needs **Node >=22.15**. Current Node 22 LTS images
|
|
146
|
+
(22.22) and Node 24 satisfy that; if your `engines.node` still declares a lower floor, raise it — the
|
|
147
|
+
override above cannot hold this copy back, because `^12.0.3` does not intersect its key.
|
|
148
|
+
|
|
149
|
+
Then `pnpm install` and `pnpm audit` — both advisories must be gone.
|
|
150
|
+
|
|
151
|
+
## 4. Native Better-Auth endpoints accept the session when the secret is auto-generated
|
|
152
|
+
|
|
153
|
+
**Affects only** projects that configure no `betterAuth.secret` and whose fallback secrets
|
|
154
|
+
(`jwt.secret`, `jwt.refresh.secret`) are shorter than 32 characters — typically a local `.env`.
|
|
155
|
+
Better-Auth then generates a secret at startup and signs with it, while the NestJS layer (cookie
|
|
156
|
+
helper, API middleware, `CoreBetterAuthService`) still signed with the configured value or none. The
|
|
157
|
+
session cookie went out unsigned, so every endpoint Better-Auth serves itself and that reads the
|
|
158
|
+
cookie — `/iam/token`, the MCP `/iam/mcp/authorize`, passkey, 2FA — answered **401 right after a
|
|
159
|
+
successful sign-in**, and an MCP OAuth login looped back to the login page.
|
|
160
|
+
|
|
161
|
+
Since 11.42.4 the NestJS layer signs with exactly the secret Better-Auth uses. No action is needed.
|
|
162
|
+
It is still the wrong setup for anything but local development: a generated secret changes on every
|
|
163
|
+
restart and differs per replica, so sessions do not survive either — set `betterAuth.secret` (at
|
|
164
|
+
least 32 characters), as the boot warning says.
|
|
165
|
+
|
|
166
|
+
## Troubleshooting
|
|
167
|
+
|
|
168
|
+
| Symptom | Cause | Fix |
|
|
169
|
+
| ---------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------- |
|
|
170
|
+
| `413 … exceeds the limit` warnings for a route that should accept it | The 100 kB default is too small for that data | Set `bodyParser.json.limit` (§1) |
|
|
171
|
+
| `bodyParser.json.limit … is not a positive size` at boot | A typo or unit the parser does not know (`'2 megabytes'`) | Use bytes or `'2mb'` |
|
|
172
|
+
| `no global json body parser is registered` at boot | The app was created with `bodyParser: false` | Remove the option, or register your own parser |
|
|
173
|
+
| A webhook test loses `req.rawBody` after configuring a limit | Test app created via the testing module with `rawBody: true` | See the `rawBody` note in §1 |
|
|
174
|
+
| A path still answers a bare `request entity too large` | A path-scoped parser of your own refused it | Raise that parser's limit in `main.ts` |
|
|
175
|
+
| `pnpm audit` still reports `@graphql-tools/utils` 12.0.0 | The override is missing, or its key was copied without the range | Add the entry from §3 verbatim |
|
|
176
|
+
| `pnpm audit` still reports `@graphql-tools/utils` 11.x | An older `@graphql-tools/schema` 10.0.x requests `^11.1.0`, outside the override key | `pnpm update --depth Infinity @graphql-tools/schema @graphql-tools/merge` (§3) — needs Node >=22.15 |
|
|
177
|
+
| `pnpm audit` still reports `proxy-addr` 2.0.7 | The lockfile was not refreshed | `pnpm update --depth Infinity proxy-addr` (§3) |
|
|
178
|
+
| `/iam/token` or MCP authorize answers 401 right after sign-in | 11.42.3 or older without `betterAuth.secret` and with short JWT secrets | Update to 11.42.4 (§4); for anything beyond local development set `betterAuth.secret` |
|
|
179
|
+
|
|
180
|
+
## Module Documentation
|
|
181
|
+
|
|
182
|
+
- [Request lifecycle — 0d. Body parsers](../docs/REQUEST-LIFECYCLE.md#0d-body-parsers)
|
|
183
|
+
- [Error codes — LTNS_0304](../docs/error-codes.md#ltns_0304-requestbodytoolarge)
|
|
184
|
+
- [Security overrides — why yours are not inherited](../docs/security-overrides.md)
|
|
185
|
+
- [Better-Auth README](../src/core/modules/better-auth/README.md) — secret resolution and fallback chain
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.42.
|
|
3
|
+
"version": "11.42.4",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"mongoose": "9.10.4",
|
|
130
130
|
"multer": "2.4.0",
|
|
131
131
|
"node-mailjet": "6.0.11",
|
|
132
|
-
"nodemailer": "10.0.
|
|
132
|
+
"nodemailer": "10.0.14",
|
|
133
133
|
"passport": "0.7.0",
|
|
134
134
|
"passport-jwt": "4.0.1",
|
|
135
135
|
"reflect-metadata": "0.2.2",
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
"oxfmt": "0.71.0",
|
|
215
215
|
"oxlint": "1.86.0",
|
|
216
216
|
"rimraf": "6.1.3",
|
|
217
|
-
"smtp-server": "3.19.
|
|
217
|
+
"smtp-server": "3.19.17",
|
|
218
218
|
"ts-node": "10.9.2",
|
|
219
219
|
"tsconfig-paths": "4.2.0",
|
|
220
220
|
"tsx": "4.23.15",
|
|
@@ -1051,6 +1051,53 @@ export interface IBetterAuthUserField {
|
|
|
1051
1051
|
type: BetterAuthFieldType;
|
|
1052
1052
|
}
|
|
1053
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* Size limits for the two body parsers NestJS registers globally (`application/json` and
|
|
1056
|
+
* `application/x-www-form-urlencoded`).
|
|
1057
|
+
*
|
|
1058
|
+
* @see IServerOptions.bodyParser
|
|
1059
|
+
* @since 11.42.4
|
|
1060
|
+
*/
|
|
1061
|
+
export interface IBodyParserConfig {
|
|
1062
|
+
/**
|
|
1063
|
+
* Limit for `application/json` bodies — REST and GraphQL alike, since Apollo reads the body the
|
|
1064
|
+
* global parser produced.
|
|
1065
|
+
*
|
|
1066
|
+
* A number is bytes; a string is a size such as `'500kb'` or `'2mb'` (1024-based, units
|
|
1067
|
+
* `b` / `kb` / `mb` / `gb`, case-insensitive).
|
|
1068
|
+
*
|
|
1069
|
+
* @default '100kb' (body-parser's own default — unset leaves the parser untouched)
|
|
1070
|
+
*/
|
|
1071
|
+
json?: IBodyParserTypeConfig;
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Limit for `application/x-www-form-urlencoded` bodies. Same value format as `json`.
|
|
1075
|
+
*
|
|
1076
|
+
* Configured separately on purpose: form posts rarely need more, and raising one limit should not
|
|
1077
|
+
* silently raise the other.
|
|
1078
|
+
*
|
|
1079
|
+
* @default '100kb' (body-parser's own default — unset leaves the parser untouched)
|
|
1080
|
+
*/
|
|
1081
|
+
urlencoded?: IBodyParserTypeConfig;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Settings for one body parser.
|
|
1086
|
+
*
|
|
1087
|
+
* @see IBodyParserConfig
|
|
1088
|
+
* @since 11.42.4
|
|
1089
|
+
*/
|
|
1090
|
+
export interface IBodyParserTypeConfig {
|
|
1091
|
+
/**
|
|
1092
|
+
* Maximum body size: bytes as a number, or a size string such as `'2mb'`.
|
|
1093
|
+
*
|
|
1094
|
+
* A value that is not a positive size (`0`, negative, `NaN`, `'lots'`) is REJECTED with an error
|
|
1095
|
+
* log and the parser keeps its default — it never becomes "no limit". The parser runs before
|
|
1096
|
+
* authentication, so an unbounded limit would be an unbounded pre-auth allocation.
|
|
1097
|
+
*/
|
|
1098
|
+
limit?: number | string;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1054
1101
|
/**
|
|
1055
1102
|
* Interface for Error Code module configuration
|
|
1056
1103
|
*
|
|
@@ -2264,6 +2311,48 @@ export interface IServerOptions {
|
|
|
2264
2311
|
*/
|
|
2265
2312
|
betterAuth?: boolean | IBetterAuth;
|
|
2266
2313
|
|
|
2314
|
+
/**
|
|
2315
|
+
* Size limits for request bodies.
|
|
2316
|
+
*
|
|
2317
|
+
* NestJS registers a JSON and a URL-encoded body parser on every application, and neither has a
|
|
2318
|
+
* limit anybody chose: body-parser's default of **100 kB** applies. A request over it is answered
|
|
2319
|
+
* with 413 in middleware — before authentication, before routing — so the whole request fails,
|
|
2320
|
+
* every retry fails identically, and a document that grew past the limit can no longer be saved.
|
|
2321
|
+
*
|
|
2322
|
+
* Unset, both parsers stay exactly as NestJS registered them. Set a limit only where the data
|
|
2323
|
+
* model needs it, and keep it as small as that need allows: the parser runs before
|
|
2324
|
+
* authentication, and `JSON.parse` blocks the event loop for the whole body — measured on a
|
|
2325
|
+
* realistic document shape, about 0.5 ms for 100 kB, 16 ms for 1 MB and 55 ms for 2 MB per
|
|
2326
|
+
* request. That is why the default is not raised for everybody.
|
|
2327
|
+
*
|
|
2328
|
+
* Applied by `CoreModule` during module init, which replaces the global parsers NestJS registered
|
|
2329
|
+
* IN PLACE — same position in the middleware chain, same options otherwise (`extended: true` for
|
|
2330
|
+
* URL-encoded bodies, `req.rawBody` when the app was created with `NestFactory.create(…, {
|
|
2331
|
+
* rawBody: true })`). Because this happens in `app.init()`, it holds under
|
|
2332
|
+
* `Test.createTestingModule(...).createNestApplication()` too, which never runs `main.ts` — a
|
|
2333
|
+
* `useBodyParser()` call in `main.ts` does not reach the e2e suite, this does. A configured value
|
|
2334
|
+
* also replaces a global parser registered in `main.ts`; a path-scoped one
|
|
2335
|
+
* (`server.use('/upload', json({ limit }))`) is left alone, and stays the way to give ONE path a
|
|
2336
|
+
* different limit, since the parser runs before routing and GraphQL shares a single path anyway.
|
|
2337
|
+
*
|
|
2338
|
+
* One caveat: under the testing module the options passed to `createNestApplication()` are not
|
|
2339
|
+
* visible to providers, so a test app created with `{ rawBody: true }` gets no `req.rawBody` from
|
|
2340
|
+
* a CONFIGURED parser. Applications created with `NestFactory.create()` are not affected.
|
|
2341
|
+
*
|
|
2342
|
+
* Independent of this setting, a 413 from either parser is answered with
|
|
2343
|
+
* `#LTNS_0304: Request body too large [<size>, limit <limit>]` instead of a bare
|
|
2344
|
+
* "request entity too large", and logged as a warning naming the route and the config key.
|
|
2345
|
+
*
|
|
2346
|
+
* @since 11.42.4
|
|
2347
|
+
*
|
|
2348
|
+
* @example
|
|
2349
|
+
* ```typescript
|
|
2350
|
+
* // Documents carry HTML: allow 2 MB of JSON, keep form posts at the default
|
|
2351
|
+
* bodyParser: { json: { limit: '2mb' } },
|
|
2352
|
+
* ```
|
|
2353
|
+
*/
|
|
2354
|
+
bodyParser?: IBodyParserConfig;
|
|
2355
|
+
|
|
2267
2356
|
/**
|
|
2268
2357
|
* Configuration for Brevo
|
|
2269
2358
|
* See: https://developers.brevo.com/
|