@lenne.tech/nest-server 11.43.3 → 11.43.5

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 —
@@ -74,6 +74,21 @@ overrides:
74
74
  '@graphql-tools/utils@>=12.0.0 <12.1.0': '12.1.0'
75
75
  ```
76
76
 
77
+ ### Advisories no override can fix yet: `minimatch` and `glob` (11.43.5)
78
+
79
+ [GHSA-g3hv-75wg-m249](https://github.com/advisories/GHSA-g3hv-75wg-m249) (minimatch `< 11.0.1`, moderate, DoS
80
+ through globparts amplification) and [GHSA-3g3m-2fjr-6pw4](https://github.com/advisories/GHSA-3g3m-2fjr-6pw4)
81
+ (glob `<= 14.0.1`, moderate) are fixed only in new majors that no consumer can take yet: `@nestjs/cli` 11.0.24, the
82
+ newest 11.x, pins glob 13.0.6 and fork-ts-checker-webpack-plugin 9.1.0 (minimatch ^3) exactly, ts-morph 28
83
+ (`@ts-morph/common` 0.29), nodemon, `@swc/cli` and glob 13 need the 9.x / 10.x lines, rimraf 6.1.3 needs glob 13 — all
84
+ on their newest release. An override across the major is no way out (minimatch 11 removed `makeRe()` and
85
+ `Minimatch#regexp`). nest-server suppresses both in its own repository with `auditConfig.unusableFixConsumers`, which
86
+ `check:overrides` re-tests on every run. What it found for its own tree: the only production path is
87
+ `@lenne.tech/nest-server > ts-morph > @ts-morph/common > minimatch`, used by the Permissions module's scanner with
88
+ globs the developer writes; every other path is dev tooling. Your project's tree may have other paths, so check
89
+ `pnpm why minimatch --prod` and `pnpm why glob --prod` before you suppress the same way, and remove the entries once
90
+ your consumers move to minimatch 11 / glob 14.
91
+
77
92
  ### Retired: `@hono/node-server` (removed 2026-08-22, nest-server 11.36.1)
78
93
 
79
94
  This list used to carry a third entry. `@modelcontextprotocol/sdk` declared `@hono/node-server@^1.19.9`
@@ -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)
@@ -0,0 +1,102 @@
1
+ # Migration Guide: 11.43.4 → 11.43.5
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** | None in the framework code |
12
+ | **New Features** | None |
13
+ | **Framework API** | Unchanged |
14
+ | **Dependencies** | `follow-redirects` 1.16.1 → 1.16.2, transitive (§1) |
15
+ | **Breaking Changes** | None |
16
+ | **Migration Effort** | Raise the version, refresh one transitive package (§1); decide on two advisories that have no usable fix yet (§2) |
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.5
23
+ pnpm update --depth Infinity follow-redirects
24
+ pnpm audit --prod
25
+ pnpm test
26
+ ```
27
+
28
+ Vendor-mode projects update the core to the tag `11.43.5` with the core updater and refresh `follow-redirects` the same
29
+ way.
30
+
31
+ ## 1. follow-redirects 1.16.2
32
+
33
+ [GHSA-mrwp-f3q8-3g9p](https://github.com/advisories/GHSA-mrwp-f3q8-3g9p) (high, CVSS 5.9): `sensitiveHeaders` arrays
34
+ from another realm are discarded, so custom credentials can follow a redirect to another host. It covers 1.16.0 and
35
+ 1.16.1, patched in 1.16.2.
36
+
37
+ nest-server reaches it through `node-mailjet > axios > follow-redirects`. axios requests a caret range, so no override is
38
+ needed, but an existing lockfile keeps 1.16.1 until it is refreshed (the command in Quick Migration). Projects that
39
+ reach axios on another path are covered by the same refresh.
40
+
41
+ ## 2. minimatch and glob: fixed only in a new major
42
+
43
+ [GHSA-g3hv-75wg-m249](https://github.com/advisories/GHSA-g3hv-75wg-m249) (minimatch `< 11.0.1`, moderate, DoS through
44
+ globparts amplification) and [GHSA-3g3m-2fjr-6pw4](https://github.com/advisories/GHSA-3g3m-2fjr-6pw4) (glob `9.0.0` to
45
+ `14.0.1`, moderate, AbortSignal cancellation bypass) were published on 2026-10-30. Both fixes exist only in new majors
46
+ (minimatch 11.0.1, glob 14.0.2), and none of their consumers in the 11.x tree can take them: `@nestjs/cli` 11.0.24, the
47
+ newest 11.x, pins glob 13.0.6 and fork-ts-checker-webpack-plugin 9.1.0 (minimatch ^3) exactly; ts-morph, nodemon,
48
+ `@swc/cli`, glob 13 and rimraf 6 are on their newest releases and request the older majors. An override across the
49
+ major is no way out: minimatch 11 removed `makeRe()` and `Minimatch#regexp`.
50
+
51
+ `pnpm audit --prod` in a project reports minimatch: ts-morph is a dependency of nest-server (the Permissions module's
52
+ scanner), so the path `@lenne.tech/nest-server > ts-morph > @ts-morph/common > minimatch` reaches every production
53
+ tree. That scanner matches globs the developer writes, never request input. glob is dev tooling in nest-server's own
54
+ tree.
55
+
56
+ What a project does depends on its audit gate:
57
+
58
+ - **No blocking `pnpm audit` gate:** nothing to do. Re-check when the consumers move to minimatch 11 / glob 14.
59
+ - **A blocking gate:** check `pnpm why minimatch --prod` and `pnpm why glob --prod` first. Your tree may reach either
60
+ package on a path that does take request input; then that path is a finding of its own. If every path matches
61
+ patterns the developer writes, suppress both advisories with the reason next to them. A project that carries
62
+ `scripts/check-overrides.mjs` (starter-based projects do) declares them in `unusableFixConsumers`, so the guard
63
+ reports the day a consumer moves:
64
+
65
+ ```yaml
66
+ # pnpm-workspace.yaml of the project — list the consumers YOUR lockfile shows, at their versions
67
+ auditConfig:
68
+ ignoreGhsas:
69
+ - GHSA-g3hv-75wg-m249
70
+ - GHSA-3g3m-2fjr-6pw4
71
+ unusableFixConsumers:
72
+ GHSA-g3hv-75wg-m249: '@swc/cli@0.8.1 + @ts-morph/common@0.29.0 + fork-ts-checker-webpack-plugin@9.1.0 + glob@13.0.6 + nodemon@3.1.14 cannot use 11.0.1'
73
+ GHSA-3g3m-2fjr-6pw4: '@nestjs/cli@11.0.24 + rimraf@6.1.3 cannot use 14.0.2'
74
+ ```
75
+
76
+ nest-server's own repository carries exactly these entries, with the reasoning in `pnpm-workspace.yaml` and a scope
77
+ guard (`tests/unit/audit-suppression-scope.spec.ts`) that fails if glob reaches the production tree or minimatch reaches
78
+ it through any parent other than `@ts-morph/common`.
79
+
80
+ ## 3. `check:overrides`: the guard of 12.x (projects that copied it)
81
+
82
+ Only relevant for a project that carries a copy of `scripts/check-overrides.mjs`. The guard on `release/11.x` is now the
83
+ one 12.x runs, which the declarations in §2 need:
84
+
85
+ - `unusableFixConsumers` takes several consumers, joined with ` + `, each at its assessed version. A listed consumer
86
+ that no longer pulls an affected version fails the run.
87
+ - Only edges to an AFFECTED version count as consumers; a parent that already pulls the fixed release does not. An edge
88
+ is matched by the package it pulls, not by its key (npm aliases).
89
+ - An advisory that names no patched version is looked up in the registry: a release outside every vulnerable range
90
+ makes the suppression `FIX AVAILABLE`, a release younger than `minimumReleaseAge` is reported as `pending`.
91
+
92
+ Copy the file from the tag `11.43.5` to take these over. Nothing changes for a project without the guard.
93
+
94
+ ## Not in this release
95
+
96
+ Fixes from 12.x that are not urgent security fixes stay there. They arrive with the move to 12:
97
+ `migration-guides/11.43.x-to-12.0.x.md`.
98
+
99
+ ## Module Documentation
100
+
101
+ - [Security overrides](../docs/security-overrides.md)
102
+ - [Package management rules](../.claude/rules/package-management.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.5",
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