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 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..cca9e223 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', function () { + assert.throws(function () { + bodyParser.json({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(function () { + bodyParser.json({ limit: '' }) + }, /option limit "" is invalid/) + }) + + 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', function () { + assert.throws(function () { + bodyParser.json({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(function () { + bodyParser.json({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', function () { + assert.throws(function () { + 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..9cff05e7 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', function () { + assert.throws(function () { + bodyParser.raw({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(function () { + bodyParser.raw({ limit: '' }) + }, /option limit "" is invalid/) + }) + + 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', function () { + assert.throws(function () { + bodyParser.raw({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(function () { + bodyParser.raw({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', function () { + assert.throws(function () { + 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..e723266b 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', function () { + assert.throws(function () { + bodyParser.text({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(function () { + bodyParser.text({ limit: '' }) + }, /option limit "" is invalid/) + }) + + 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', function () { + assert.throws(function () { + bodyParser.text({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(function () { + bodyParser.text({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', function () { + assert.throws(function () { + 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..f8542ae2 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', function () { + assert.throws(function () { + bodyParser.urlencoded({ limit: 'invalid' }) + }, /option limit "invalid" is invalid/) + assert.throws(function () { + bodyParser.urlencoded({ limit: '' }) + }, /option limit "" is invalid/) + }) + + 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', function () { + assert.throws(function () { + bodyParser.urlencoded({ limit: true }) + }, /option limit "true" is invalid/) + assert.throws(function () { + bodyParser.urlencoded({ limit: false }) + }, /option limit "false" is invalid/) + }) + + it('should throw an error for an object limit', function () { + assert.throws(function () { + 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' }))