From 240e3422ad8dfe416fe156eac999d3cbb08a9758 Mon Sep 17 00:00:00 2001 From: CoderLuii Date: Wed, 29 Jul 2026 15:53:27 -0400 Subject: [PATCH] fix: update upload and websocket dependencies --- eslint.config.js | 1 + package-lock.json | 211 +++++++++++------- package.json | 8 +- server/modules/assets/assets.routes.ts | 3 + server/modules/file-tree/file-tree.module.ts | 2 + server/modules/voice/voice.module.ts | 7 +- server/shared/multipart-upload-limits.ts | 1 + .../tests/multipart-upload-limits.test.ts | 52 +++++ 8 files changed, 197 insertions(+), 88 deletions(-) create mode 100644 server/shared/multipart-upload-limits.ts create mode 100644 server/shared/tests/multipart-upload-limits.test.ts diff --git a/eslint.config.js b/eslint.config.js index b5c60196c0..d050dba5e6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -162,6 +162,7 @@ export default tseslint.config( "server/shared/frontmatter.ts", "server/shared/claude-cli-path.ts", "server/shared/image-attachments.ts", + "server/shared/multipart-upload-limits.ts", ], // classify shared utility files so modules can depend on them explicitly mode: "file", }, diff --git a/package-lock.json b/package-lock.json index 5a559b4123..efb2ec2360 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,8 +39,8 @@ "cmdk": "^1.1.1", "cors": "^2.8.5", "cross-spawn": "^7.0.3", - "dompurify": "^3.4.7", - "express": "^4.18.2", + "dompurify": "^3.4.12", + "express": "^4.22.2", "fuse.js": "^7.0.0", "gray-matter": "^4.0.3", "i18next": "^25.7.4", @@ -51,7 +51,7 @@ "katex": "^0.16.25", "lucide-react": "^0.515.0", "mime-types": "^3.0.1", - "multer": "^2.0.1", + "multer": "^2.2.0", "node-fetch": "^2.7.0", "node-pty": "^1.2.0-beta.12", "react": "^18.2.0", @@ -69,7 +69,7 @@ "remark-math": "^6.0.0", "tailwind-merge": "^3.3.1", "web-push": "^3.6.7", - "ws": "^8.14.2" + "ws": "^8.21.1" }, "bin": { "cloudcli": "dist-server/server/modules/cli/cli.js" @@ -4053,22 +4053,6 @@ "node": ">= 0.6" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", @@ -8049,23 +8033,23 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -8081,12 +8065,41 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -10156,9 +10169,9 @@ "optional": true }, "node_modules/dompurify": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.7.tgz", - "integrity": "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -11587,39 +11600,39 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -16389,6 +16402,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "devOptional": true, "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -16410,21 +16424,22 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz", + "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" + "type-is": "^1.6.18" }, "engines": { "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mute-stream": { @@ -17508,9 +17523,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/path-type": { @@ -18164,12 +18179,13 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -18231,20 +18247,49 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -20144,14 +20189,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -20163,13 +20208,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -23206,9 +23251,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index 92dcf6d7b3..1f5e287402 100644 --- a/package.json +++ b/package.json @@ -164,8 +164,8 @@ "cmdk": "^1.1.1", "cors": "^2.8.5", "cross-spawn": "^7.0.3", - "dompurify": "^3.4.7", - "express": "^4.18.2", + "dompurify": "^3.4.12", + "express": "^4.22.2", "fuse.js": "^7.0.0", "gray-matter": "^4.0.3", "i18next": "^25.7.4", @@ -176,7 +176,7 @@ "katex": "^0.16.25", "lucide-react": "^0.515.0", "mime-types": "^3.0.1", - "multer": "^2.0.1", + "multer": "^2.2.0", "node-fetch": "^2.7.0", "node-pty": "^1.2.0-beta.12", "react": "^18.2.0", @@ -194,7 +194,7 @@ "remark-math": "^6.0.0", "tailwind-merge": "^3.3.1", "web-push": "^3.6.7", - "ws": "^8.14.2" + "ws": "^8.21.1" }, "devDependencies": { "@commitlint/cli": "^20.5.0", diff --git a/server/modules/assets/assets.routes.ts b/server/modules/assets/assets.routes.ts index 35f174d95c..f14367b253 100644 --- a/server/modules/assets/assets.routes.ts +++ b/server/modules/assets/assets.routes.ts @@ -8,6 +8,7 @@ import { isAllowedImageMimeType, openStoredAttachmentAsset, } from '@/modules/assets/services/image-assets.service.js'; +import { FLAT_MULTIPART_FIELD_NESTING_DEPTH } from '@/shared/multipart-upload-limits.js'; const router = express.Router(); @@ -36,6 +37,7 @@ const upload = multer({ } }, limits: { + fieldNestingDepth: FLAT_MULTIPART_FIELD_NESTING_DEPTH, fileSize: 5 * 1024 * 1024, // 5MB files: 5, }, @@ -44,6 +46,7 @@ const upload = multer({ const attachmentUpload = multer({ storage, limits: { + fieldNestingDepth: FLAT_MULTIPART_FIELD_NESTING_DEPTH, fileSize: 10 * 1024 * 1024, files: 10, }, diff --git a/server/modules/file-tree/file-tree.module.ts b/server/modules/file-tree/file-tree.module.ts index a35ca829ab..0fd4e2ed1c 100644 --- a/server/modules/file-tree/file-tree.module.ts +++ b/server/modules/file-tree/file-tree.module.ts @@ -8,6 +8,7 @@ import multer from 'multer'; import { projectsDb } from '@/modules/database/index.js'; import { createFileTreeRouter } from '@/modules/file-tree/file-tree.routes.js'; import { createFileTreeService } from '@/modules/file-tree/file-tree.service.js'; +import { FLAT_MULTIPART_FIELD_NESTING_DEPTH } from '@/shared/multipart-upload-limits.js'; import type { FileTreeFileSystem, FileTreeLogger, @@ -92,6 +93,7 @@ const fileUploadMiddleware = multer({ }, }), limits: { + fieldNestingDepth: FLAT_MULTIPART_FIELD_NESTING_DEPTH, fileSize: MAXIMUM_UPLOAD_SIZE_BYTES, files: MAXIMUM_UPLOAD_FILE_COUNT, }, diff --git a/server/modules/voice/voice.module.ts b/server/modules/voice/voice.module.ts index 33afd88459..d89313d729 100644 --- a/server/modules/voice/voice.module.ts +++ b/server/modules/voice/voice.module.ts @@ -1,5 +1,7 @@ import multer from 'multer'; +import { FLAT_MULTIPART_FIELD_NESTING_DEPTH } from '@/shared/multipart-upload-limits.js'; + import { createVoiceRouter } from './voice.routes.js'; import { createVoiceService } from './voice.service.js'; @@ -37,7 +39,10 @@ const voiceService = createVoiceService({ const audioUpload = multer({ storage: multer.memoryStorage(), - limits: { fileSize: 25 * 1024 * 1024 }, + limits: { + fieldNestingDepth: FLAT_MULTIPART_FIELD_NESTING_DEPTH, + fileSize: 25 * 1024 * 1024, + }, }); /** Voice router assembled for the server entrypoint. */ diff --git a/server/shared/multipart-upload-limits.ts b/server/shared/multipart-upload-limits.ts new file mode 100644 index 0000000000..37db28396d --- /dev/null +++ b/server/shared/multipart-upload-limits.ts @@ -0,0 +1 @@ +export const FLAT_MULTIPART_FIELD_NESTING_DEPTH = 0; diff --git a/server/shared/tests/multipart-upload-limits.test.ts b/server/shared/tests/multipart-upload-limits.test.ts new file mode 100644 index 0000000000..b5476dc4c9 --- /dev/null +++ b/server/shared/tests/multipart-upload-limits.test.ts @@ -0,0 +1,52 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import test from 'node:test'; + +import express from 'express'; +import multer from 'multer'; + +import { FLAT_MULTIPART_FIELD_NESTING_DEPTH } from '../multipart-upload-limits.js'; + +test('flat multipart uploads reject nested fields', async () => { + const upload = multer({ + storage: multer.memoryStorage(), + limits: { + fieldNestingDepth: FLAT_MULTIPART_FIELD_NESTING_DEPTH, + }, + }); + const app = express(); + app.post('/upload', (request, response) => { + upload.none()(request, response, (error: unknown) => { + const code = ( + typeof error === 'object' + && error !== null + && 'code' in error + && typeof error.code === 'string' + ) + ? error.code + : 'NO_ERROR'; + response.statusCode = code === 'LIMIT_FIELD_NESTING' ? 422 : 500; + response.end(code); + }); + }); + + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + const address = server.address(); + assert.ok(address && typeof address === 'object'); + + try { + const form = new FormData(); + form.set('nested[value]', 'blocked'); + const response = await fetch(`http://127.0.0.1:${address.port}/upload`, { + method: 'POST', + body: form, + }); + + assert.equal(response.status, 422); + assert.equal(await response.text(), 'LIMIT_FIELD_NESTING'); + } finally { + server.close(); + await once(server, 'close'); + } +});