@lenne.tech/nest-server 11.43.3 → 11.43.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.
@@ -19,7 +19,7 @@ used to **exact-pin**, to a vulnerable version:
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
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 — the framework's own floor since 11.42.10, which is why the snippet below targets 12.0.3 |
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
+ | `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`. [GHSA-q7qp-rjg7-q26m](https://github.com/advisories/GHSA-q7qp-rjg7-q26m) — high: DoS via a pending asynchronous `fileFilter` after request teardown, covers 2.4.0, patched `>=2.5.0` (published 2026-10-23) | `@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 to 11.2.8, the newest 11.x, pin `2.4.0`, which GHSA-q7qp-rjg7-q26m covers. No 11.x update reaches the fix, so **this entry is load-bearing again since 11.43.4** |
23
23
 
24
24
  **Status since 11.42.4:** `@graphql-tools/utils` joins `js-yaml` as load-bearing — no `@nestjs/graphql`
25
25
  13.x resolves past the exact pin. The 11.42.4 release also covers `proxy-addr` (GHSA-jqcg-44mw-7w3h,
@@ -58,10 +58,10 @@ overrides:
58
58
  # Remove once @nestjs/swagger pins >=5.4.1 itself.
59
59
  'js-yaml@>=5.0.0 <5.4.2': '5.4.2'
60
60
 
61
- # @nestjs/platform-express <=11.2.5 exact-pins multer@2.2.0 (GHSA-wc9g-mqfw-jrwm and three more, patched >=2.3.0).
62
- # Keep the target in LOCKSTEP with the multer version @lenne.tech/nest-server declares (2.4.0 since 11.41.4).
63
- # Inert on @nestjs/platform-express 11.2.6+, which pins 2.4.0 itself.
64
- 'multer@>=2.0.0 <2.4.0': '2.4.0'
61
+ # @nestjs/platform-express 11.x exact-pins multer (<=11.2.5: 2.2.0, 11.2.6 to 11.2.8: 2.4.0).
62
+ # GHSA-q7qp-rjg7-q26m (high, DoS) covers 2.4.0, patched >=2.5.0. LOAD-BEARING again since 11.43.4.
63
+ # Keep the target in LOCKSTEP with the multer version @lenne.tech/nest-server declares (2.5.0 since 11.43.4).
64
+ 'multer@>=2.0.0 <2.5.0': '2.5.0'
65
65
 
66
66
  # @nestjs/graphql 13.4.5 exact-pins @graphql-tools/utils@12.0.0 — GHSA-7mx3-vvmw-hjmv (high, patched >=12.0.1).
67
67
  # Target the newest 12.x, so the caret requesters (@graphql-tools/schema / merge) are not held back —
@@ -0,0 +1,119 @@
1
+ # Migration Guide: 11.43.3 → 11.43.4
2
+
3
+ A security patch for projects that still run on 11.x. It is published under the npm dist-tag `latest-11`, not `latest`:
4
+ 12.x stays the version a plain install resolves to, and moving every project to 12.x remains the goal. This release
5
+ carries only urgent security fixes; functional fixes from 12.x are not backported.
6
+
7
+ ## Overview
8
+
9
+ | Category | Details |
10
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | **Bugfixes** | A legacy logout ends the device's refresh token; with `allDevices` it ends every device (§1). The legacy REST rate limit can no longer be bypassed with a query string (§1b) |
12
+ | **New Features** | None |
13
+ | **Framework API** | Unchanged |
14
+ | **Dependencies** | `nodemailer` 10.1.2 → 10.1.4 (§2), `multer` 2.4.0 → 2.5.0 (§3) |
15
+ | **Breaking Changes** | None |
16
+ | **Migration Effort** | Raise the version, add or raise the `multer` override (§3), refresh four transitive packages (§4) |
17
+
18
+ ## Quick Migration
19
+
20
+ ```bash
21
+ # npm mode: the version is pinned exactly, and `latest` is 12.x, so name the version
22
+ pnpm add @lenne.tech/nest-server@11.43.4
23
+ ```
24
+
25
+ Then add the `multer` override from §3 to `pnpm-workspace.yaml` (or raise the one you already have), raise the
26
+ `body-parser` and `fast-uri` entries from §4 if your project carries them, and refresh:
27
+
28
+ ```bash
29
+ pnpm install
30
+ pnpm update --depth Infinity body-parser zod fast-uri @hono/node-server
31
+ pnpm audit --prod
32
+ pnpm test
33
+ ```
34
+
35
+ Vendor-mode projects update the core to the tag `11.43.4` with the core updater and apply §3 and §4 the same way.
36
+
37
+ ## 1. A legacy logout did not end the refresh token
38
+
39
+ `CoreAuthService.logout()` removed the device from `refreshTokens` and wrote the map back through `update()`. `update()`
40
+ merges its input into the stored record, so a key that is merely missing from the input survives: the logged-out
41
+ device's refresh token kept working, and `logout(allDevices)`, which wrote an empty map, left every device signed in.
42
+ A user who signed out, or signed out everywhere after losing a device, stayed reachable for whoever held the refresh
43
+ token.
44
+
45
+ The logout now writes `null` for every device it ends, in `refreshTokens` and `tempTokens`; `validateUser()` reads a
46
+ `null` device as signed out. No action is needed. A project that overrides `logout()` and writes the token maps itself
47
+ must do the same: write `null` for an ended device, never delete its key.
48
+
49
+ This affects every project that keeps the legacy endpoints open (`auth.legacyEndpoints`, or the three-argument
50
+ `CoreModule.forRoot()` from before 11.38.0). IAM (Better-Auth) sessions are not affected.
51
+
52
+ ## 1b. The legacy REST rate limit counted every spelling of a route separately
53
+
54
+ `LegacyAuthRateLimitGuard` keyed the REST counter on the last segment of `request.url`, query string included. Express
55
+ routes `/auth/signin?n=1`, `/auth/signin?n=2` and `/auth/SignIn/` to the same handler, but each got a fresh bucket, so
56
+ `auth.rateLimit` never stopped password guessing against `/auth/signin` (or sign-up spam against `/auth/signup`). The
57
+ counter is now keyed on the route handler. No action is needed; projects with `auth.rateLimit` configured and the legacy
58
+ REST endpoints open are the ones that were exposed.
59
+
60
+ ## 2. nodemailer 10.1.4
61
+
62
+ Three advisories in nodemailer's address parser, all moderate (CVSS 6.5), all patched in 10.1.4: a nested comment
63
+ ([GHSA-8c8m-p3m8-cjg8](https://github.com/advisories/GHSA-8c8m-p3m8-cjg8)), a `;` inside a quoted string
64
+ ([GHSA-m943-m63m-w62w](https://github.com/advisories/GHSA-m943-m63m-w62w)) and a `>` inside a quoted local part
65
+ ([GHSA-85x2-q946-cp5h](https://github.com/advisories/GHSA-85x2-q946-cp5h)) each turn text of an address into an
66
+ additional recipient. nest-server pins nodemailer itself, so raising the framework is enough. A project that declares
67
+ its own `nodemailer`, or carries an override for it, raises that to 10.1.4 as well.
68
+
69
+ ## 3. multer 2.5.0 — the override is needed again
70
+
71
+ [GHSA-q7qp-rjg7-q26m](https://github.com/advisories/GHSA-q7qp-rjg7-q26m) (high, CVSS 7.5): a pending asynchronous
72
+ `fileFilter` after the request was torn down leads to a denial of service. It covers 2.4.0, patched in 2.5.0.
73
+
74
+ nest-server now declares 2.5.0, but every `@nestjs/platform-express` 11.x up to 11.2.8, the newest, pins multer
75
+ **exactly** (2.4.0), and `FileInterceptor` uses that copy. No 11.x update reaches the fix, so the project needs an
76
+ override:
77
+
78
+ ```yaml
79
+ # pnpm-workspace.yaml of the project
80
+ overrides:
81
+ # @nestjs/platform-express 11.x exact-pins multer 2.4.0 (GHSA-q7qp-rjg7-q26m, fixed 2.5.0)
82
+ 'multer@>=2.0.0 <2.5.0': '2.5.0'
83
+ ```
84
+
85
+ A project that already mirrors the older entry (`'multer@>=2.0.0 <2.4.0': '2.4.0'`) must raise both key and target:
86
+ the old target is the vulnerable version.
87
+
88
+ ## 4. Transitive packages: refresh the lockfile
89
+
90
+ Four advisories of the same week sit in packages nest-server does not pin; they are fixed by a lockfile refresh in the
91
+ project (the command in Quick Migration):
92
+
93
+ | Package | Advisory | Patched |
94
+ | ------- | -------- | ------- |
95
+ | `body-parser` (via `@apollo/server`, express) | [GHSA-27r3-jxmh-p828](https://github.com/advisories/GHSA-27r3-jxmh-p828), high | 2.4.0 |
96
+ | `zod` (via `@modelcontextprotocol/sdk`) | [GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5), high | 4.6.6 |
97
+ | `fast-uri` (via ajv) | four advisories up to moderate | 3.1.9 |
98
+ | `@hono/node-server` (via `@modelcontextprotocol/sdk`) | [GHSA-rmxm-3fg6-px4f](https://github.com/advisories/GHSA-rmxm-3fg6-px4f), moderate | 2.1.3 |
99
+
100
+ **A project that carries an override for `body-parser` or `fast-uri`** (starter-based projects do) must raise it, or
101
+ the old target keeps the vulnerable version in place:
102
+
103
+ ```yaml
104
+ 'body-parser@>=2.0.0 <2.4.0': '2.4.0' # was '<2.3.0': '2.3.0'
105
+ 'fast-uri@<3.1.9': '3.1.9' # was '<3.1.8': '3.1.8'
106
+ ```
107
+
108
+ `pnpm run check:overrides`, where a project has it, reports each of these as `TOO LOW`.
109
+
110
+ ## Not in this release
111
+
112
+ Fixes from 12.x that are not urgent security fixes stay there, among them the configuration dump of a second
113
+ `new ConfigService()` (12.0.0) and the fixes for `distinct()` over ObjectId fields and for names lost at an IAM sign-in
114
+ (12.1.0). They arrive with the move to 12: `migration-guides/11.43.x-to-12.0.x.md`.
115
+
116
+ ## Module Documentation
117
+
118
+ - [Auth (Legacy)](../src/core/modules/auth/)
119
+ - [Security overrides](../docs/security-overrides.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.43.3",
3
+ "version": "11.43.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",
@@ -127,9 +127,9 @@
127
127
  "lodash": "4.18.1",
128
128
  "mongodb": "7.7.0",
129
129
  "mongoose": "9.11.2",
130
- "multer": "2.4.0",
130
+ "multer": "2.5.0",
131
131
  "node-mailjet": "6.0.11",
132
- "nodemailer": "10.1.2",
132
+ "nodemailer": "10.1.4",
133
133
  "passport": "0.7.0",
134
134
  "passport-jwt": "4.0.1",
135
135
  "reflect-metadata": "0.2.2",
@@ -213,7 +213,7 @@
213
213
  "oxfmt": "0.73.0",
214
214
  "oxlint": "1.88.0",
215
215
  "rimraf": "6.1.3",
216
- "smtp-server": "3.19.23",
216
+ "smtp-server": "3.21.1",
217
217
  "ts-node": "10.9.2",
218
218
  "tsconfig-paths": "4.2.0",
219
219
  "tsx": "4.23.15",
@@ -76,9 +76,10 @@ export class LegacyAuthRateLimitGuard implements CanActivate {
76
76
  const request = context.switchToHttp().getRequest();
77
77
  const ip = this.getClientIp(request);
78
78
 
79
- // Get endpoint from URL path
80
- const url = request.url || request.path || '';
81
- const endpoint = url.split('/').pop() || 'unknown';
79
+ // The handler, never the URL: the URL carries the query string and the client's spelling of the path
80
+ // (Express routes case-insensitively and ignores a trailing slash), so every variant was a fresh bucket and
81
+ // `/auth/signin?n=1`, `?n=2`, … guessed passwords without ever reaching the limit.
82
+ const endpoint = context.getHandler()?.name || 'unknown';
82
83
 
83
84
  return { endpoint, ip };
84
85
  }
@@ -130,16 +130,30 @@ export class CoreAuthService {
130
130
  throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
131
131
  }
132
132
 
133
+ // A device is ended by writing `null` for it, not by deleting its key: update() MERGES the input into the stored
134
+ // record, so a key that is merely missing from the input survives — and with it the refresh token this logout
135
+ // must end. `validateUser()` reads a `null` device as "not signed in".
136
+ const ended = (deviceIds: string[]) => Object.fromEntries(deviceIds.map((id) => [id, null]));
137
+
133
138
  // Logout from all devices
134
139
  if (serviceOptions.allDevices) {
140
+ const deviceIds = Object.keys(user.refreshTokens);
135
141
  user.refreshTokens = {};
136
- await this.userService.update(user.id, { refreshTokens: {} }, serviceOptions);
142
+ await this.userService.update(
143
+ user.id,
144
+ { refreshTokens: ended(deviceIds), tempTokens: ended(Object.keys(user.tempTokens ?? {})) },
145
+ serviceOptions,
146
+ );
137
147
  return true;
138
148
  }
139
149
 
140
150
  // Logout from specific devices
141
151
  delete user.refreshTokens[deviceId];
142
- await this.userService.update(user.id, { refreshTokens: user.refreshTokens }, serviceOptions);
152
+ await this.userService.update(
153
+ user.id,
154
+ { refreshTokens: ended([deviceId]), tempTokens: ended([deviceId]) },
155
+ serviceOptions,
156
+ );
143
157
  return true;
144
158
  }
145
159