VYPR
Low severity3.1NVD Advisory· Published Apr 26, 2025· Updated May 13, 2026

CVE-2025-46653

CVE-2025-46653

Description

Formidable (aka node-formidable) 2.1.0 through 3.x before 3.5.3 relies on hexoid to prevent guessing of filenames for untrusted executable content; however, hexoid is documented as not "cryptographically secure." (Also, there is a scenario in which only the last two characters of a hexoid string need to be guessed, but this is not often relevant.) NOTE: this does not imply that, in a typical use case, attackers will be able to exploit any hexoid behavior to upload and execute their own content.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
formidablenpm
>= 3.1.1-canary.20211030, < 3.5.33.5.3
formidablenpm
>= 2.1.0, < 2.1.32.1.3

Affected products

1
  • node-formidable/Formidablev5
    Range: 2.1.0

Patches

2
37a3e89fca1e

fix: switch hexoid to cuid2; try fixing tests - fail (#992)

https://github.com/node-formidable/formidableCharlike Mike ReagentApr 19, 2025via ghsa
7 files changed · +1732 1870
  • package.json+4 32 modified
    @@ -1,6 +1,6 @@
     {
       "name": "formidable",
    -  "version": "2.1.1",
    +  "version": "2.1.2",
       "license": "MIT",
       "description": "A node.js module for parsing form data, especially file uploads.",
       "homepage": "https://github.com/node-formidable/formidable",
    @@ -30,14 +30,12 @@
         "test:jest": "jest --coverage"
       },
       "dependencies": {
    +    "@paralleldrive/cuid2": "^2.2.2",
         "dezalgo": "^1.0.4",
    -    "hexoid": "^1.0.0",
         "once": "^1.4.0",
         "qs": "^6.11.0"
       },
       "devDependencies": {
    -    "@commitlint/cli": "8.3.5",
    -    "@commitlint/config-conventional": "8.3.4",
         "@tunnckocore/prettier-config": "1.3.8",
         "del-cli": "3.0.0",
         "eslint": "6.8.0",
    @@ -47,9 +45,8 @@
         "eslint-plugin-prettier": "3.1.3",
         "express": "4.17.1",
         "husky": "4.2.5",
    -    "jest": "25.4.0",
    +    "jest": "27.2.4",
         "koa": "2.11.0",
    -    "lint-staged": "10.2.7",
         "make-dir-cli": "2.0.0",
         "nyc": "15.0.1",
         "prettier": "2.0.5",
    @@ -69,30 +66,5 @@
         "json",
         "ulpoad",
         "file"
    -  ],
    -  "husky": {
    -    "hooks": {
    -      "pre-commit": "git status --porcelain && yarn lint-staged",
    -      "commit-msg": "yarn commitlint -E HUSKY_GIT_PARAMS"
    -    }
    -  },
    -  "commitlint": {
    -    "extends": [
    -      "@commitlint/config-conventional"
    -    ]
    -  },
    -  "lint-staged": {
    -    "!*.{js,jsx,ts,tsx}": [
    -      "yarn run fmt:prepare"
    -    ],
    -    "*.{js,jsx,ts,tsx}": [
    -      "yarn run lint"
    -    ]
    -  },
    -  "renovate": {
    -    "extends": [
    -      "@tunnckocore",
    -      ":pinAllExceptPeerDependencies"
    -    ]
    -  }
    +  ]
     }
    
  • README.md+99 74 modified
    @@ -2,30 +2,47 @@
       <img alt="npm formidable package logo" src="https://raw.githubusercontent.com/node-formidable/formidable/master/logo.png" />
     </p>
     
    -# formidable [![npm version][npmv-img]][npmv-url] [![MIT license][license-img]][license-url] [![Libera Manifesto][libera-manifesto-img]][libera-manifesto-url] [![Twitter][twitter-img]][twitter-url]
    +# formidable [![npm version][npmv-img]][npmv-url] [![MIT license][license-img]][license-url] [![Libera Manifesto][libera-manifesto-img]][libera-manifesto-url]
     
     > A Node.js module for parsing form data, especially file uploads.
     
    +> [!CAUTION] As of April 2025, old versions like v1 and v2 are still the most
    +> used, while they are deperecated for years -- they are also vulnerable to
    +> attacks if you are not implementing it properly. **Please upgrade!** We are
    +> here to help, and AI Editors & Agents could help a lot in such codemod-like
    +> migrations.
    +
    +> [!TIP] If you are starting a fresh project, try `formidable@latest` (v3) or
    +> you can check out the `formidable-mini` which is a super minimal version of
    +> Formidable (not quite configurable yet, but when it does it could become the
    +> basis for `formidable@v4`), using web standards like FormData API and File
    +> API, and you can use it to stream uploads directly to S3 or other such
    +> services.
    +
     [![Code style][codestyle-img]][codestyle-url]
    -[![codecoverage][codecov-img]][codecov-url]
     [![linux build status][linux-build-img]][build-url]
    -[![windows build status][windows-build-img]][build-url]
     [![macos build status][macos-build-img]][build-url]
     
     If you have any _how-to_ kind of questions, please read the [Contributing
     Guide][contributing-url] and [Code of Conduct][code_of_conduct-url]
     documents.<br /> For bugs reports and feature requests, [please create an
    -issue][open-issue-url] or ping [@tunnckoCore / @3a1FcBx0](https://twitter.com/3a1FcBx0)
    -at Twitter.
    +issue][open-issue-url] or ping
    +[@wgw_eth / @wgw_lol](https://twitter.com/wgw_eth) at Twitter.
     
     [![Conventional Commits][ccommits-img]][ccommits-url]
     [![Minimum Required Nodejs][nodejs-img]][npmv-url]
    +[![Buy me a Kofi][kofi-img]][kofi-url]
    +[![Make A Pull Request][prs-welcome-img]][prs-welcome-url]
    +[![Twitter][twitter-img]][twitter-url]
    +
    +<!-- [![Conventional Commits][ccommits-img]][ccommits-url]
    +[![Minimum Required Nodejs][nodejs-img]][npmv-url]
     [![Tidelift Subcsription][tidelift-img]][tidelift-url]
     [![Buy me a Kofi][kofi-img]][kofi-url]
     [![Renovate App Status][renovateapp-img]][renovateapp-url]
    -[![Make A Pull Request][prs-welcome-img]][prs-welcome-url]
    +[![Make A Pull Request][prs-welcome-img]][prs-welcome-url] -->
     
    -This project is [semantically versioned](https://semver.org) and available as
    +<!-- This project is [semantically versioned](https://semver.org) and available as
     part of the [Tidelift Subscription][tidelift-url] for professional grade
     assurances, enhanced support and security.
     [Learn more.](https://tidelift.com/subscription/pkg/npm-formidable?utm_source=npm-formidable&utm_medium=referral&utm_campaign=enterprise)
    @@ -34,14 +51,16 @@ _The maintainers of `formidable` and thousands of other packages are working
     with Tidelift to deliver commercial support and maintenance for the Open Source
     dependencies you use to build your applications. Save time, reduce risk, and
     improve code health, while paying the maintainers of the exact dependencies you
    -use._
    +use._ -->
     
     [![][npm-weekly-img]][npmv-url] [![][npm-monthly-img]][npmv-url]
     [![][npm-yearly-img]][npmv-url] [![][npm-alltime-img]][npmv-url]
     
     ## Project Status: Maintained
     
    -_Check [VERSION NOTES](https://github.com/node-formidable/formidable/blob/master/VERSION_NOTES.md) for more information on v1, v2, and v3 plans, NPM dist-tags and branches._
    +_Check
    +[VERSION NOTES](https://github.com/node-formidable/formidable/blob/master/VERSION_NOTES.md)
    +for more information on v1, v2, and v3 plans, NPM dist-tags and branches._
     
     This module was initially developed by
     [**@felixge**](https://github.com/felixge) for
    @@ -73,7 +92,9 @@ This project requires `Node.js >= 10.13`. Install it using
     recommend to use Yarn when you think to contribute to this project._
     
     This is a low-level package, and if you're using a high-level framework it _may_
    -already be included. Check the examples below and the [examples/](https://github.com/node-formidable/formidable/tree/master/examples) folder.
    +already be included. Check the examples below and the
    +[examples/](https://github.com/node-formidable/formidable/tree/master/examples)
    +folder.
     
     ```sh
     # v2
    @@ -85,8 +106,8 @@ npm install formidable@v2
     npm install formidable@v3
     ```
     
    -_**Note:** In near future v3 will be published on the `latest` NPM dist-tag. Future not ready releases will continue to be published on `canary` dist-tag._
    -
    +_**Note:** In near future v3 will be published on the `latest` NPM dist-tag.
    +Future not ready releases will continue to be published on `canary` dist-tag._
     
     ## Examples
     
    @@ -325,12 +346,13 @@ See it's defaults in [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js)
       uploaded file.
     - `options.maxFileSize` **{number}** - default `200 * 1024 * 1024` (200mb);
       limit the size of uploaded file.
    -- `options.maxFields` **{number}** - default `1000`; limit the number of fields, set 0 for unlimited
    +- `options.maxFields` **{number}** - default `1000`; limit the number of fields,
    +  set 0 for unlimited
     - `options.maxFieldsSize` **{number}** - default `20 * 1024 * 1024` (20mb);
       limit the amount of memory all fields together (except files) can allocate in
       bytes.
    -- `options.hashAlgorithm` **{string | false}** - default `false`; include checksums calculated
    -  for incoming files, set this to some hash algorithm, see
    +- `options.hashAlgorithm` **{string | false}** - default `false`; include
    +  checksums calculated for incoming files, set this to some hash algorithm, see
       [crypto.createHash](https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options)
       for available algorithms
     - `options.fileWriteStreamHandler` **{function}** - default `null`, which by
    @@ -354,8 +376,7 @@ See it's defaults in [src/Formidable.js DEFAULT_OPTIONS](./src/Formidable.js)
     - `options.filter` **{function}** - default function that always returns true.
       Use it to filter files before they are uploaded. Must return a boolean.
     
    -
    -#### `options.filename`  **{function}** function (name, ext, part, form) -> string
    +#### `options.filename` **{function}** function (name, ext, part, form) -> string
     
     _**Note:** If this size of combined fields, or size of some file is exceeded, an
     `'error'` event is fired._
    @@ -370,20 +391,19 @@ form.bytesReceived;
     form.bytesExpected;
     ```
     
    -#### `options.filter`  **{function}** function ({name, originalFilename, mimetype}) -> boolean
    +#### `options.filter` **{function}** function ({name, originalFilename, mimetype}) -> boolean
     
    -**Note:** use an outside variable to cancel all uploads upon the first error 
    +**Note:** use an outside variable to cancel all uploads upon the first error
     
     ```js
     const options = {
    -  filter: function ({name, originalFilename, mimetype}) {
    +  filter: function ({ name, originalFilename, mimetype }) {
         // keep only images
    -    return mimetype && mimetype.includes("image");
    -  }
    +    return mimetype && mimetype.includes('image');
    +  },
     };
     ```
     
    -
     ### .parse(request, callback)
     
     Parses an incoming Node.js `request` containing form data. If `callback` is
    @@ -405,41 +425,41 @@ multipart stream. Doing so will disable any `'field'` / `'file'` events
     processing which would occur otherwise, making you fully responsible for
     handling the processing.
     
    -About `uploadDir`, given the following directory structure 
    +About `uploadDir`, given the following directory structure
    +
     ```
     project-name
     ├── src
     │   └── server.js
    -│       
    +│
     └── uploads
         └── image.jpg
     ```
     
     `__dirname` would be the same directory as the source file itself (src)
     
    -
     ```js
    - `${__dirname}/../uploads`
    +`${__dirname}/../uploads`;
     ```
     
     to put files in uploads.
     
    -Omitting `__dirname` would make the path relative to the current working directory. This would be the same if server.js is launched from src but not project-name.
    -
    +Omitting `__dirname` would make the path relative to the current working
    +directory. This would be the same if server.js is launched from src but not
    +project-name.
     
     `null` will use default which is `os.tmpdir()`
     
    -Note: If the directory does not exist, the uploaded files are __silently discarded__. To make sure it exists:
    +Note: If the directory does not exist, the uploaded files are **silently
    +discarded**. To make sure it exists:
     
     ```js
    -import {createNecessaryDirectoriesSync} from "filesac";
    -
    +import { createNecessaryDirectoriesSync } from 'filesac';
     
     const uploadPath = `${__dirname}/../uploads`;
     createNecessaryDirectoriesSync(`${uploadPath}/x`);
     ```
     
    -
     In the example below, we listen on couple of events and direct them to the
     `data` listener, so you can do whatever you choose there, based on whether its
     before the file been emitted, the header value, the header name, on field, on
    @@ -468,30 +488,33 @@ form.once('end', () => {
     });
     
     // If you want to customize whatever you want...
    -form.on('data', ({ name, key, value, buffer, start, end, formname, ...more }) => {
    -  if (name === 'partBegin') {
    -  }
    -  if (name === 'partData') {
    -  }
    -  if (name === 'headerField') {
    -  }
    -  if (name === 'headerValue') {
    -  }
    -  if (name === 'headerEnd') {
    -  }
    -  if (name === 'headersEnd') {
    -  }
    -  if (name === 'field') {
    -    console.log('field name:', key);
    -    console.log('field value:', value);
    -  }
    -  if (name === 'file') {
    -    console.log('file:', formname, value);
    -  }
    -  if (name === 'fileBegin') {
    -    console.log('fileBegin:', formname, value);
    -  }
    -});
    +form.on(
    +  'data',
    +  ({ name, key, value, buffer, start, end, formname, ...more }) => {
    +    if (name === 'partBegin') {
    +    }
    +    if (name === 'partData') {
    +    }
    +    if (name === 'headerField') {
    +    }
    +    if (name === 'headerValue') {
    +    }
    +    if (name === 'headerEnd') {
    +    }
    +    if (name === 'headersEnd') {
    +    }
    +    if (name === 'field') {
    +      console.log('field name:', key);
    +      console.log('field value:', value);
    +    }
    +    if (name === 'file') {
    +      console.log('file:', formname, value);
    +    }
    +    if (name === 'fileBegin') {
    +      console.log('fileBegin:', formname, value);
    +    }
    +  },
    +);
     ```
     
     ### .use(plugin: Plugin)
    @@ -603,7 +626,7 @@ export interface File {
     
       // The name this file had according to the uploading client.
       file.originalFilename: string | null;
    -  
    +
       // calculated based on options provided
       file.newFilename: string | null;
     
    @@ -653,12 +676,12 @@ file system.
     
     ```js
     form.on('fileBegin', (formName, file) => {
    -    // accessible here 
    -    // formName the name in the form (<input name="thisname" type="file">) or http filename for octetstream
    -    // file.originalFilename http filename or null if there was a parsing error
    -    // file.newFilename generated hexoid or what options.filename returned
    -    // file.filepath default pathnme as per options.uploadDir and options.filename
    -    // file.filepath = CUSTOM_PATH // to change the final path
    +  // accessible here
    +  // formName the name in the form (<input name="thisname" type="file">) or http filename for octetstream
    +  // file.originalFilename http filename or null if there was a parsing error
    +  // file.newFilename generated hexoid or what options.filename returned
    +  // file.filepath default pathnme as per options.uploadDir and options.filename
    +  // file.filepath = CUSTOM_PATH // to change the final path
     });
     ```
     
    @@ -669,9 +692,9 @@ Emitted whenever a field / file pair has been received. `file` is an instance of
     
     ```js
     form.on('file', (formname, file) => {
    -    // same as fileBegin, except
    -    // it is too late to change file.filepath
    -    // file.hash is available if options.hash was used
    +  // same as fileBegin, except
    +  // it is too late to change file.filepath
    +  // file.hash is available if options.hash was used
     });
     ```
     
    @@ -766,8 +789,10 @@ Thanks goes to these wonderful people
     From a [Felix blog post](https://felixge.de/2013/03/11/the-pull-request-hack/):
     
     - [Sven Lito](https://github.com/svnlto) for fixing bugs and merging patches
    -- [egirshov](https://github.com/egirshov) for contributing many improvements to the node-formidable multipart parser
    -- [Andrew Kelley](https://github.com/superjoe30) for also helping with fixing bugs and making improvements
    +- [egirshov](https://github.com/egirshov) for contributing many improvements to
    +  the node-formidable multipart parser
    +- [Andrew Kelley](https://github.com/superjoe30) for also helping with fixing
    +  bugs and making improvements
     - [Mike Frey](https://github.com/mikefrey) for contributing JSON support
     
     ## License
    @@ -795,8 +820,8 @@ Formidable is licensed under the [MIT License][license-url].
     [renovateapp-img]: https://badgen.net/badge/renovate/enabled/green?cache=300
     [prs-welcome-img]: https://badgen.net/badge/PRs/welcome/green?cache=300
     [prs-welcome-url]: http://makeapullrequest.com
    -[twitter-url]: https://twitter.com/3a1fcBx0
    -[twitter-img]: https://badgen.net/twitter/follow/3a1fcBx0?icon=twitter&color=1da1f2&cache=300
    +[twitter-url]: https://twitter.com/wgw_eth
    +[twitter-img]: https://badgen.net/badge/twitter/follow/wgw_eth?icon=twitter&color=1da1f2&cache=30
     
     [npm-weekly-img]: https://badgen.net/npm/dw/formidable?icon=npm&cache=300
     [npm-monthly-img]: https://badgen.net/npm/dm/formidable?icon=npm&cache=300
    @@ -819,8 +844,8 @@ Formidable is licensed under the [MIT License][license-url].
     [kofi-url]: https://ko-fi.com/tunnckoCore/commissions
     [kofi-img]: https://badgen.net/badge/ko-fi/support/29abe0c2?cache=300&icon=https://rawcdn.githack.com/tunnckoCore/badgen-icons/f8264c6414e0bec449dd86f2241d50a9b89a1203/icons/kofi.svg
     
    -[linux-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master/ubuntu?cache=300&label=linux%20build&icon=github
    -[macos-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master/macos?cache=300&label=macos%20build&icon=github
    +[linux-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master?label=linux%20build&icon=github
    +[macos-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master?label=macos%20build&icon=github
     [windows-build-img]: https://badgen.net/github/checks/node-formidable/formidable/master/windows?cache=300&label=windows%20build&icon=github
    -[build-url]: https://github.com/node-formidable/formidable/actions?query=workflow%3Anodejs
    +[build-url]: https://github.com/node-formidable/formidable/actions
     <!-- prettier-ignore-end -->
    
  • src/Formidable.js+15 9 modified
    @@ -5,14 +5,21 @@
     
     const os = require('os');
     const path = require('path');
    -const hexoid = require('hexoid');
    +const cuid2 = require('@paralleldrive/cuid2');
     const once = require('once');
     const dezalgo = require('dezalgo');
     const { EventEmitter } = require('events');
     const { StringDecoder } = require('string_decoder');
     const qs = require('qs');
     
    -const toHexoId = hexoid(25);
    +const CUID2_FINGERPRINT = `${
    +  process.env.NODE_ENV
    +}-${os.platform()}-${os.hostname()}-${os.machine()}`;
    +const createId = cuid2.init({
    +  length: 25,
    +  fingerprint: CUID2_FINGERPRINT.toLowerCase(),
    +});
    +
     const DEFAULT_OPTIONS = {
       maxFields: 1000,
       maxFieldsSize: 20 * 1024 * 1024,
    @@ -27,7 +34,7 @@ const DEFAULT_OPTIONS = {
       enabledPlugins: ['octetstream', 'querystring', 'multipart', 'json'],
       fileWriteStreamHandler: null,
       defaultInvalidName: 'invalid-name',
    -  filter: function () {
    +  filter() {
         return true;
       },
     };
    @@ -541,8 +548,6 @@ class IncomingForm extends EventEmitter {
         return basename.slice(firstDot, lastDot) + extname;
       }
     
    -
    -
       _joinDirectoryName(name) {
         const newPath = path.join(this.uploadDir, name);
     
    @@ -571,15 +576,16 @@ class IncomingForm extends EventEmitter {
           };
         } else {
           this._getNewName = (part) => {
    -        const name = toHexoId();
    +        const name = createId();
     
             if (part && this.options.keepExtensions) {
    -          const originalFilename = typeof part === 'string' ? part : part.originalFilename;
    +          const originalFilename =
    +            typeof part === 'string' ? part : part.originalFilename;
               return `${name}${this._getExtension(originalFilename)}`;
             }
    -    
    +
             return name;
    -      }
    +      };
         }
       }
     
    
  • test/integration/fixtures.test.js+41 13 modified
    @@ -9,16 +9,34 @@ const path = require('path');
     const http = require('http');
     const assert = require('assert');
     
    +const dezalgo = require('dezalgo');
    +const { once } = require('process');
     const formidable = require('../../src/index');
     
    -const PORT = 13534;
    +let server;
    +const PORT = 13536;
     const CWD = process.cwd();
     const FIXTURES_PATH = path.join(CWD, 'test', 'fixture', 'js');
     const FIXTURES_HTTP = path.join(CWD, 'test', 'fixture', 'http');
     const UPLOAD_DIR = path.join(CWD, 'test', 'tmp');
     
    +beforeEach(() => {
    +  server = http.createServer();
    +});
    +
    +afterEach(
    +  () =>
    +    new Promise((resolve) => {
    +      server.close(() => {
    +        server = null;
    +        resolve();
    +      });
    +    }),
    +);
    +
     test('fixtures', (done) => {
    -  const server = http.createServer();
    +  const callback = once(dezalgo(done));
    +  jest.setTimeout(8000);
       server.listen(PORT, findFixtures);
     
       function findFixtures() {
    @@ -59,7 +77,9 @@ test('fixtures', (done) => {
         uploadFixture(fixtureName, (err, parts) => {
           if (err) {
             err.fixtureName = fixtureName;
    -        throw err;
    +        // throw err;
    +        callback(err);
    +        return;
           }
     
           fixture.forEach((expectedPart, i) => {
    @@ -69,7 +89,10 @@ test('fixtures', (done) => {
     
             if (parsedPart.type === 'file') {
               const file = parsedPart.value;
    -          assert.strictEqual(file.originalFilename, expectedPart.originalFilename);
    +          assert.strictEqual(
    +            file.originalFilename,
    +            expectedPart.originalFilename,
    +          );
     
               if (expectedPart.sha1) {
                 assert.strictEqual(
    @@ -85,7 +108,7 @@ test('fixtures', (done) => {
         });
       }
     
    -  function uploadFixture(fixtureName, cb) {
    +  function uploadFixture(fixtureName, cback) {
         server.once('request', (req, res) => {
           const form = formidable({
             uploadDir: UPLOAD_DIR,
    @@ -94,17 +117,20 @@ test('fixtures', (done) => {
           });
           form.parse(req);
     
    -      function callback(...args) {
    -        const realCallback = cb;
    -        // eslint-disable-next-line no-param-reassign
    -        cb = function calbackFn() {};
    +      // function callback(...args) {
    +      //   const realCallback = cb;
    +      //   // eslint-disable-next-line no-param-reassign
    +      //   cb = function calbackFn() {};
     
    -        realCallback(...args);
    -      }
    +      //   realCallback(...args);
    +      // }
     
           const parts = [];
           form
    -        .on('error', callback)
    +        .on('error', (er) => {
    +          callback(er);
    +          // done(er);
    +        })
             .on('fileBegin', (name, value) => {
               parts.push({ type: 'file', name, value });
             })
    @@ -113,7 +139,9 @@ test('fixtures', (done) => {
             })
             .on('end', () => {
               res.end();
    -          callback(null, parts);
    +          cback(null, parts);
    +          callback();
    +          // done();
             });
         });
     
    
  • test/standalone/connection-aborted.test.js+25 9 modified
    @@ -5,10 +5,27 @@ const http = require('http');
     const net = require('net');
     const formidable = require('../../src/index');
     
    -const PORT = 13539;
    +let port = 13533;
    +let server;
    +
    +beforeEach(() => {
    +  server = http.createServer();
    +  port += 1;
    +});
    +
    +afterEach(
    +  () =>
    +    new Promise((resolve) => {
    +      if (server.listening) {
    +        server.close(() => resolve());
    +      } else {
    +        resolve();
    +      }
    +    }),
    +);
     
     test('connection aborted', (done) => {
    -  const server = http.createServer((req) => {
    +  server.on('request', (req) => {
         const form = formidable();
     
         let abortedReceived = false;
    @@ -17,7 +34,9 @@ test('connection aborted', (done) => {
         });
         form.on('error', () => {
           assert(abortedReceived, 'Error event should follow aborted');
    -      server.close();
    +      if (server) {
    +        server.close();
    +      }
         });
         form.on('end', () => {
           throw new Error('Unexpected "end" event');
    @@ -27,19 +46,16 @@ test('connection aborted', (done) => {
             abortedReceived,
             'from .parse() callback: Error event should follow aborted',
           );
    -
    -      server.close();
           done();
         });
       });
     
    -  server.listen(PORT, 'localhost', () => {
    -    const choosenPort = server.address().port;
    -
    -    const client = net.connect(choosenPort);
    +  server.listen(port, 'localhost', () => {
    +    const client = net.connect(port);
     
         client.write(
           'POST / HTTP/1.1\r\n' +
    +        'Host: localhost\r\n' +
             'Content-Length: 70\r\n' +
             'Content-Type: multipart/form-data; boundary=foo\r\n\r\n',
         );
    
  • test/standalone/keep-alive-error.test.js+28 7 modified
    @@ -7,13 +7,35 @@ const http = require('http');
     const assert = require('assert');
     const formidable = require('../../src/index');
     
    +let server;
    +let port = 13538;
     let ok = 0;
     let errors = 0;
     
    -const PORT = 0;
    +beforeEach(() => {
    +  server = http.createServer();
    +  ok = 0;
    +  errors = 0;
    +  port += 1;
    +});
    +
    +afterEach(
    +  () =>
    +    new Promise((resolve) => {
    +      if (server.listening) {
    +        server.close(() => {
    +          server = null;
    +          resolve();
    +        });
    +      } else {
    +        resolve();
    +      }
    +    }),
    +);
     
    +// Original test untouched from here
     test('keep alive error', (done) => {
    -  const server = http.createServer((req, res) => {
    +  server.on('request', (req, res) => {
         const form = formidable();
         form.on('error', () => {
           errors += 1;
    @@ -28,7 +50,7 @@ test('keep alive error', (done) => {
         form.parse(req);
       });
     
    -  server.listen(PORT, () => {
    +  server.listen(port, () => {
         const choosenPort = server.address().port;
     
         const client = net.createConnection(choosenPort);
    @@ -66,13 +88,12 @@ test('keep alive error', (done) => {
             clientTwo.end();
     
             setTimeout(() => {
    -          // ? yup, quite true, it makes sense to be 2
               assert.strictEqual(ok, 2, `should "ok" count === 2, has: ${ok}`);
     
               server.close();
               done();
    -        }, 300);
    -      }, 200);
    -    }, 150);
    +        }, 400);
    +      }, 300);
    +    }, 200);
       });
     });
    
  • yarn.lock+1520 1726 modified
022c2c5577df

fix: switch hexoid - cuid2 -> better, safer, more random; this is addressing a vulnerability report

https://github.com/node-formidable/formidabletunnckoCoreApr 18, 2025via ghsa
6 files changed · +12550 5888
  • CHANGELOG.md+11 3 modified
    @@ -1,5 +1,13 @@
     # Changelog
     
    +### 3.5.3
    +
    +- update failing tests
    +- update CI/CD workflows and actions;
    +- update CodeQL github action for security analysis
    +- update readme, links and badges
    +- update to use cuid2 (battle-tested `@paralleldrive/cuid2` package) for better random names - should not be breaking anything since it's still 25 characters long, but a lot safer and faster.
    +
     ### 3.5.2
     
      * fix: ([#982](https://github.com/node-formidable/formidable/pull/982)) make it easier to import hexoid with webpack
    @@ -63,7 +71,7 @@
      * feat: add maxTotalFileSize, default is maxFileSize (for backwards compatibility)
      * fix: minFileSize is per file
      * fix: allowEmptyFiles fix in cases where one file is not empty
    - * fix: allowEmptyFiles false option by default 
    + * fix: allowEmptyFiles false option by default
      * fix: rename wrongly named error
      * refactor: rename wrongly named maxFileSize into maxTotalFileSize
     
    @@ -92,7 +100,7 @@
      * fields with [] in the name do not receive special treatment
      * remove unused qs and querystring dependency
      * feat: Use ES modules ([#727](https://github.com/node-formidable/formidable/pull/727))
    - * options.enabledPlugins must contain the plugin themselves instead of the plugins names 
    + * options.enabledPlugins must contain the plugin themselves instead of the plugins names
     
     
     ### 2.0.0
    @@ -126,7 +134,7 @@
      * feat: custom file (re)naming, thru options.filename ([#591](https://github.com/node-formidable/node-formidable/pull/591), [#84](https://github.com/node-formidable/node-formidable/issues/84), [#86](https://github.com/node-formidable/node-formidable/issues/86), [#94](https://github.com/node-formidable/node-formidable/issues/94), [#154](https://github.com/node-formidable/node-formidable/issues/154), [#158](https://github.com/node-formidable/node-formidable/issues/158), [#488](https://github.com/node-formidable/node-formidable/issues/488), [#595](https://github.com/node-formidable/node-formidable/issues/595))
     
     
    - 
    +
     ### v1.2.1 (2018-03-20)
     
      * `maxFileSize` option with default of 200MB (Charlike Mike Reagent, Nima Shahri)
    
  • package.json+1 1 modified
    @@ -68,8 +68,8 @@
         "test:ci": "node --disable-warning=ExperimentalWarning --experimental-vm-modules node_modules/.bin/nyc jest --testPathPattern=test/ --coverage && node --disable-warning=ExperimentalWarning --experimental-vm-modules node_modules/.bin/nyc node --test test-node/"
       },
       "dependencies": {
    +    "@paralleldrive/cuid2": "^2.2.2",
         "dezalgo": "^1.0.4",
    -    "hexoid": "^2.0.0",
         "once": "^1.4.0"
       },
       "devDependencies": {
    
  • package-lock.json+12507 0 added
  • src/Formidable.js+10 9 modified
    @@ -1,23 +1,24 @@
     /* eslint-disable class-methods-use-this */
     /* eslint-disable no-underscore-dangle */
     
    +import { init as cuid2init } from '@paralleldrive/cuid2';
    +import dezalgo from 'dezalgo';
    +import { EventEmitter } from 'node:events';
    +import fsPromises from 'node:fs/promises';
     import os from 'node:os';
     import path from 'node:path';
    -import fsPromises from 'node:fs/promises';
    -import { EventEmitter } from 'node:events';
     import { StringDecoder } from 'node:string_decoder';
    -import { hexoid } from 'hexoid';
     import once from 'once';
    -import dezalgo from 'dezalgo';
    -import { octetstream, querystring, multipart, json } from './plugins/index.js';
    +import FormidableError, * as errors from './FormidableError.js';
     import PersistentFile from './PersistentFile.js';
     import VolatileFile from './VolatileFile.js';
     import DummyParser from './parsers/Dummy.js';
     import MultipartParser from './parsers/Multipart.js';
    -import * as errors from './FormidableError.js';
    -import FormidableError from './FormidableError.js';
    +import { json, multipart, octetstream, querystring } from './plugins/index.js';
    +
    +const CUID2_FINGERPRINT = `${process.env.NODE_ENV}-${os.platform()}-${os.hostname()}-${os.machine()}`
    +const createId = cuid2init({ length: 25, fingerprint: CUID2_FINGERPRINT.toLowerCase() });
     
    -const toHexoId = hexoid(25);
     const DEFAULT_OPTIONS = {
       maxFields: 1000,
       maxFieldsSize: 20 * 1024 * 1024,
    @@ -622,7 +623,7 @@ class IncomingForm extends EventEmitter {
           };
         } else {
           this._getNewName = (part) => {
    -        const name = toHexoId();
    +        const name = createId();
     
             if (part && this.options.keepExtensions) {
               const originalFilename =
    
  • test/standalone/connection-aborted.test.js+21 10 modified
    @@ -3,10 +3,26 @@ import { createServer } from 'node:http';
     import { connect } from 'node:net';
     import formidable from '../../src/index.js';
     
    -const PORT = 13540;
    +let server;
    +let port = 13540;
    +
    +beforeEach(() => {
    +  server = createServer();
    +  port += 1;
    +});
    +
    +afterEach(() => {
    +  return new Promise((resolve) => {
    +    if (server.listening) {
    +      server.close(() => resolve());
    +    } else {
    +      resolve();
    +    }
    +  });
    +});
     
     test('connection aborted', (done) => {
    -  const server = createServer((req) => {
    +  server.on('request', (req) => {
         const form = formidable();
     
         let abortedReceived = false;
    @@ -24,17 +40,12 @@ test('connection aborted', (done) => {
             abortedReceived,
             'from .parse() callback: Error event should follow aborted',
           );
    -
    -      server.close(() => {
    -        done();
    -      });
    +      done();
         });
       });
     
    -  server.listen(PORT, 'localhost', () => {
    -    const chosenPort = server.address().port;
    -
    -    const client = connect(chosenPort);
    +  server.listen(port, 'localhost', () => {
    +    const client = connect(port);
     
         client.write(
           'POST / HTTP/1.1\r\n' +
    
  • yarn.lock+0 5865 removed

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.