From d7d2c27c13bb7b5342c57ee865e76f0ce2d045cb Mon Sep 17 00:00:00 2001 From: Phillip Barta Date: Mon, 6 Jul 2026 17:59:50 +0200 Subject: [PATCH 1/3] fix: improve limit option validation --- HISTORY.md | 8 +++++++- lib/types/json.js | 10 +++++++--- lib/types/raw.js | 10 +++++++--- lib/types/text.js | 10 +++++++--- lib/types/urlencoded.js | 10 +++++++--- test/json.js | 30 ++++++++++++++++++++++++++++++ test/raw.js | 30 ++++++++++++++++++++++++++++++ test/text.js | 30 ++++++++++++++++++++++++++++++ test/urlencoded.js | 30 ++++++++++++++++++++++++++++++ 9 files changed, 155 insertions(+), 13 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index e5c4fde6..ed073ba5 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,12 @@ +unreleased +=================== +* fix: improve `limit` option validation (#698) + * Invalid `limit` values (e.g. unparseable strings or `NaN`) now throw instead of being silently ignored, which previously disabled size limit enforcement + * `null` and `undefined` fall back to the default 100kb limit + 1.20.5 / 2026-04-24 =================== -* refactor(json): simplify strict mode error string construction +* refactor(json): simplify strict mode error string construction * fix: extended urlencoded parsing of arrays with >100 elements (#716) * deps: qs@~6.15.1 diff --git a/lib/types/json.js b/lib/types/json.js index d1f510d9..f1802434 100644 --- a/lib/types/json.js +++ b/lib/types/json.js @@ -53,15 +53,19 @@ var JSON_SYNTAX_REGEXP = /#+/g function json (options) { var opts = options || {} - var limit = typeof opts.limit !== 'number' - ? bytes.parse(opts.limit || '100kb') - : opts.limit + var limit = typeof opts.limit === 'undefined' || opts.limit === null + ? 102400 // 100kb default + : bytes.parse(opts.limit) var inflate = opts.inflate !== false var reviver = opts.reviver var strict = opts.strict !== false var type = opts.type || 'application/json' var verify = opts.verify || false + if (limit === null) { + throw new TypeError('option limit "' + String(opts.limit) + '" is invalid') + } + if (verify !== false && typeof verify !== 'function') { throw new TypeError('option verify must be function') } diff --git a/lib/types/raw.js b/lib/types/raw.js index f5d1b674..2b3e06d7 100644 --- a/lib/types/raw.js +++ b/lib/types/raw.js @@ -33,12 +33,16 @@ function raw (options) { var opts = options || {} var inflate = opts.inflate !== false - var limit = typeof opts.limit !== 'number' - ? bytes.parse(opts.limit || '100kb') - : opts.limit + var limit = typeof opts.limit === 'undefined' || opts.limit === null + ? 102400 // 100kb default + : bytes.parse(opts.limit) var type = opts.type || 'application/octet-stream' var verify = opts.verify || false + if (limit === null) { + throw new TypeError('option limit "' + String(opts.limit) + '" is invalid') + } + if (verify !== false && typeof verify !== 'function') { throw new TypeError('option verify must be function') } diff --git a/lib/types/text.js b/lib/types/text.js index 083a0090..5247a925 100644 --- a/lib/types/text.js +++ b/lib/types/text.js @@ -35,12 +35,16 @@ function text (options) { var defaultCharset = opts.defaultCharset || 'utf-8' var inflate = opts.inflate !== false - var limit = typeof opts.limit !== 'number' - ? bytes.parse(opts.limit || '100kb') - : opts.limit + var limit = typeof opts.limit === 'undefined' || opts.limit === null + ? 102400 // 100kb default + : bytes.parse(opts.limit) var type = opts.type || 'text/plain' var verify = opts.verify || false + if (limit === null) { + throw new TypeError('option limit "' + String(opts.limit) + '" is invalid') + } + if (verify !== false && typeof verify !== 'function') { throw new TypeError('option verify must be function') } diff --git a/lib/types/urlencoded.js b/lib/types/urlencoded.js index 892e3469..52ebe5f9 100644 --- a/lib/types/urlencoded.js +++ b/lib/types/urlencoded.js @@ -50,12 +50,16 @@ function urlencoded (options) { var extended = opts.extended !== false var inflate = opts.inflate !== false - var limit = typeof opts.limit !== 'number' - ? bytes.parse(opts.limit || '100kb') - : opts.limit + var limit = typeof opts.limit === 'undefined' || opts.limit === null + ? 102400 // 100kb default + : bytes.parse(opts.limit) var type = opts.type || 'application/x-www-form-urlencoded' var verify = opts.verify || false + if (limit === null) { + throw new TypeError('option limit "' + String(opts.limit) + '" is invalid') + } + if (verify !== false && typeof verify !== 'function') { throw new TypeError('option verify must be function') } diff --git a/test/json.js b/test/json.js index c76ea138..3d2992da 100644 --- a/test/json.js +++ b/test/json.js @@ -130,6 +130,36 @@ describe('bodyParser.json()', function () { }) describe('with limit option', function () { + it('should throw an error for an invalid string limit', () => { + assert.throws(() => { + bodyParser.json({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(() => { + bodyParser.json({ limit: '' }) + }, /option limit "" is invalid/) + }) + + it('should throw an error for a NaN limit', () => { + assert.throws(() => { + bodyParser.json({ limit: NaN }) + }, /option limit "NaN" is invalid/) + }) + + it('should throw an error for a boolean limit', () => { + assert.throws(() => { + bodyParser.json({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(() => { + bodyParser.json({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', () => { + assert.throws(() => { + bodyParser.json({ limit: { foo: 'bar' } }) + }, /option limit "\[object Object\]" is invalid/) + }) + it('should 413 when over limit with Content-Length', function (done) { var buf = Buffer.alloc(1024, '.') request(createServer({ limit: '1kb' })) diff --git a/test/raw.js b/test/raw.js index d2ab027f..25e0ffa5 100644 --- a/test/raw.js +++ b/test/raw.js @@ -88,6 +88,36 @@ describe('bodyParser.raw()', function () { }) describe('with limit option', function () { + it('should throw an error for an invalid string limit', () => { + assert.throws(() => { + bodyParser.raw({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(() => { + bodyParser.raw({ limit: '' }) + }, /option limit "" is invalid/) + }) + + it('should throw an error for a NaN limit', () => { + assert.throws(() => { + bodyParser.raw({ limit: NaN }) + }, /option limit "NaN" is invalid/) + }) + + it('should throw an error for a boolean limit', () => { + assert.throws(() => { + bodyParser.raw({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(() => { + bodyParser.raw({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', () => { + assert.throws(() => { + bodyParser.raw({ limit: { foo: 'bar' } }) + }, /option limit "\[object Object\]" is invalid/) + }) + it('should 413 when over limit with Content-Length', function (done) { var buf = Buffer.alloc(1028, '.') var server = createServer({ limit: '1kb' }) diff --git a/test/text.js b/test/text.js index c3c55180..96877fd0 100644 --- a/test/text.js +++ b/test/text.js @@ -106,6 +106,36 @@ describe('bodyParser.text()', function () { }) describe('with limit option', function () { + it('should throw an error for an invalid string limit', () => { + assert.throws(() => { + bodyParser.text({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(() => { + bodyParser.text({ limit: '' }) + }, /option limit "" is invalid/) + }) + + it('should throw an error for a NaN limit', () => { + assert.throws(() => { + bodyParser.text({ limit: NaN }) + }, /option limit "NaN" is invalid/) + }) + + it('should throw an error for a boolean limit', () => { + assert.throws(() => { + bodyParser.text({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(() => { + bodyParser.text({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', () => { + assert.throws(() => { + bodyParser.text({ limit: { foo: 'bar' } }) + }, /option limit "\[object Object\]" is invalid/) + }) + it('should 413 when over limit with Content-Length', function (done) { var buf = Buffer.alloc(1028, '.') request(createServer({ limit: '1kb' })) diff --git a/test/urlencoded.js b/test/urlencoded.js index 970c1e12..cdd6fafc 100644 --- a/test/urlencoded.js +++ b/test/urlencoded.js @@ -323,6 +323,36 @@ describe('bodyParser.urlencoded()', function () { }) describe('with limit option', function () { + it('should throw an error for an invalid string limit', () => { + assert.throws(() => { + bodyParser.urlencoded({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(() => { + bodyParser.urlencoded({ limit: '' }) + }, /option limit "" is invalid/) + }) + + it('should throw an error for a NaN limit', () => { + assert.throws(() => { + bodyParser.urlencoded({ limit: NaN }) + }, /option limit "NaN" is invalid/) + }) + + it('should throw an error for a boolean limit', () => { + assert.throws(() => { + bodyParser.urlencoded({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(() => { + bodyParser.urlencoded({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', () => { + assert.throws(() => { + bodyParser.urlencoded({ limit: { foo: 'bar' } }) + }, /option limit "\[object Object\]" is invalid/) + }) + it('should 413 when over limit with Content-Length', function (done) { var buf = Buffer.alloc(1024, '.') request(createServer({ limit: '1kb' })) From 82088ab3f2b84b3380ae978d13a517ae42fb8ea6 Mon Sep 17 00:00:00 2001 From: Phillip Barta Date: Mon, 6 Jul 2026 18:05:14 +0200 Subject: [PATCH 2/3] chore: replace arrow functions in tests --- test/json.js | 20 ++++++++++---------- test/raw.js | 20 ++++++++++---------- test/text.js | 20 ++++++++++---------- test/urlencoded.js | 20 ++++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/test/json.js b/test/json.js index 3d2992da..cca9e223 100644 --- a/test/json.js +++ b/test/json.js @@ -130,32 +130,32 @@ describe('bodyParser.json()', function () { }) describe('with limit option', function () { - it('should throw an error for an invalid string limit', () => { - assert.throws(() => { + it('should throw an error for an invalid string limit', function () { + assert.throws(function () { bodyParser.json({ limit: 'invalid' }) }, /option limit "invalid" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.json({ limit: '' }) }, /option limit "" is invalid/) }) - it('should throw an error for a NaN limit', () => { - assert.throws(() => { + it('should throw an error for a NaN limit', function () { + assert.throws(function () { bodyParser.json({ limit: NaN }) }, /option limit "NaN" is invalid/) }) - it('should throw an error for a boolean limit', () => { - assert.throws(() => { + it('should throw an error for a boolean limit', function () { + assert.throws(function () { bodyParser.json({ limit: true }) }, /option limit "true" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.json({ limit: false }) }, /option limit "false" is invalid/) }) - it('should throw an error for an object limit', () => { - assert.throws(() => { + it('should throw an error for an object limit', function () { + assert.throws(function () { bodyParser.json({ limit: { foo: 'bar' } }) }, /option limit "\[object Object\]" is invalid/) }) diff --git a/test/raw.js b/test/raw.js index 25e0ffa5..9cff05e7 100644 --- a/test/raw.js +++ b/test/raw.js @@ -88,32 +88,32 @@ describe('bodyParser.raw()', function () { }) describe('with limit option', function () { - it('should throw an error for an invalid string limit', () => { - assert.throws(() => { + it('should throw an error for an invalid string limit', function () { + assert.throws(function () { bodyParser.raw({ limit: 'invalid' }) }, /option limit "invalid" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.raw({ limit: '' }) }, /option limit "" is invalid/) }) - it('should throw an error for a NaN limit', () => { - assert.throws(() => { + it('should throw an error for a NaN limit', function () { + assert.throws(function () { bodyParser.raw({ limit: NaN }) }, /option limit "NaN" is invalid/) }) - it('should throw an error for a boolean limit', () => { - assert.throws(() => { + it('should throw an error for a boolean limit', function () { + assert.throws(function () { bodyParser.raw({ limit: true }) }, /option limit "true" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.raw({ limit: false }) }, /option limit "false" is invalid/) }) - it('should throw an error for an object limit', () => { - assert.throws(() => { + it('should throw an error for an object limit', function () { + assert.throws(function () { bodyParser.raw({ limit: { foo: 'bar' } }) }, /option limit "\[object Object\]" is invalid/) }) diff --git a/test/text.js b/test/text.js index 96877fd0..e723266b 100644 --- a/test/text.js +++ b/test/text.js @@ -106,32 +106,32 @@ describe('bodyParser.text()', function () { }) describe('with limit option', function () { - it('should throw an error for an invalid string limit', () => { - assert.throws(() => { + it('should throw an error for an invalid string limit', function () { + assert.throws(function () { bodyParser.text({ limit: 'invalid' }) }, /option limit "invalid" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.text({ limit: '' }) }, /option limit "" is invalid/) }) - it('should throw an error for a NaN limit', () => { - assert.throws(() => { + it('should throw an error for a NaN limit', function () { + assert.throws(function () { bodyParser.text({ limit: NaN }) }, /option limit "NaN" is invalid/) }) - it('should throw an error for a boolean limit', () => { - assert.throws(() => { + it('should throw an error for a boolean limit', function () { + assert.throws(function () { bodyParser.text({ limit: true }) }, /option limit "true" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.text({ limit: false }) }, /option limit "false" is invalid/) }) - it('should throw an error for an object limit', () => { - assert.throws(() => { + it('should throw an error for an object limit', function () { + assert.throws(function () { bodyParser.text({ limit: { foo: 'bar' } }) }, /option limit "\[object Object\]" is invalid/) }) diff --git a/test/urlencoded.js b/test/urlencoded.js index cdd6fafc..f8542ae2 100644 --- a/test/urlencoded.js +++ b/test/urlencoded.js @@ -323,32 +323,32 @@ describe('bodyParser.urlencoded()', function () { }) describe('with limit option', function () { - it('should throw an error for an invalid string limit', () => { - assert.throws(() => { + it('should throw an error for an invalid string limit', function () { + assert.throws(function () { bodyParser.urlencoded({ limit: 'invalid' }) }, /option limit "invalid" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.urlencoded({ limit: '' }) }, /option limit "" is invalid/) }) - it('should throw an error for a NaN limit', () => { - assert.throws(() => { + it('should throw an error for a NaN limit', function () { + assert.throws(function () { bodyParser.urlencoded({ limit: NaN }) }, /option limit "NaN" is invalid/) }) - it('should throw an error for a boolean limit', () => { - assert.throws(() => { + it('should throw an error for a boolean limit', function () { + assert.throws(function () { bodyParser.urlencoded({ limit: true }) }, /option limit "true" is invalid/) - assert.throws(() => { + assert.throws(function () { bodyParser.urlencoded({ limit: false }) }, /option limit "false" is invalid/) }) - it('should throw an error for an object limit', () => { - assert.throws(() => { + it('should throw an error for an object limit', function () { + assert.throws(function () { bodyParser.urlencoded({ limit: { foo: 'bar' } }) }, /option limit "\[object Object\]" is invalid/) }) From 3b654b608f745e2f1aa07d8e4d6752f0c2961868 Mon Sep 17 00:00:00 2001 From: Phillip Barta Date: Mon, 6 Jul 2026 18:06:14 +0200 Subject: [PATCH 3/3] ci: add Node.js 26 to ci matrix --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cace4f34..0655d4cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,9 @@ jobs: - name: Node.js 25.x node-version: "25" + - name: Node.js 26.x + node-version: "26" + steps: - uses: actions/checkout@v4