From 344573f899fb04b5ea3799c4e0f4e1b8f249ac2c Mon Sep 17 00:00:00 2001 From: Mohammad Faiz Date: Fri, 29 May 2026 02:54:51 +0530 Subject: [PATCH 1/2] fix: handle non-finite numeric maxAge in res.cookie() Infinity, -Infinity, and NaN maxAge values now gracefully produce a session cookie instead of throwing an unhandled TypeError from cookie.serialize(). Non-numeric invalid maxAge values (e.g. 'foobar') still throw as before. --- lib/response.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/response.js b/lib/response.js index f965e539dd2..77a3aad98c9 100644 --- a/lib/response.js +++ b/lib/response.js @@ -759,7 +759,10 @@ res.cookie = function (name, value, options) { if (opts.maxAge != null) { var maxAge = opts.maxAge - 0 - if (!isNaN(maxAge)) { + if (maxAge === Infinity || maxAge === -Infinity || (typeof opts.maxAge === 'number' && isNaN(maxAge))) { + // strip non-finite numeric maxAge (Infinity, -Infinity, NaN) + delete opts.maxAge + } else if (!isNaN(maxAge)) { opts.expires = new Date(Date.now() + maxAge) opts.maxAge = Math.floor(maxAge / 1000) } From 1c53e59955944371a22c70cbe8fe901f7267672d Mon Sep 17 00:00:00 2001 From: Mohammad Faiz Date: Sat, 13 Jun 2026 01:30:42 +0530 Subject: [PATCH 2/2] fix: add tests and clarify typeof guard in res.cookie() maxAge handling Add tests for Infinity, -Infinity, and NaN maxAge values producing session cookies (no Max-Age) as requested in PR review feedback. Expand the inline comment to explain why the typeof guard is needed: it distinguishes numeric NaN (stripped to session cookie) from non-numeric strings like 'foobar' (which pass through and throw). Also documents that string 'Infinity' coerces to numeric Infinity and is intentionally handled the same way. --- lib/response.js | 7 ++++++- test/res.cookie.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/response.js b/lib/response.js index 77a3aad98c9..08c5ea8e925 100644 --- a/lib/response.js +++ b/lib/response.js @@ -760,7 +760,12 @@ res.cookie = function (name, value, options) { var maxAge = opts.maxAge - 0 if (maxAge === Infinity || maxAge === -Infinity || (typeof opts.maxAge === 'number' && isNaN(maxAge))) { - // strip non-finite numeric maxAge (Infinity, -Infinity, NaN) + // strip non-finite numeric maxAge (Infinity, -Infinity, NaN) so the + // cookie falls back to a session cookie (no Max-Age). The typeof + // guard ensures non-numeric strings like 'foobar' still flow through + // to cookie.serialize() and throw, rather than being silently dropped. + // (String 'Infinity' coerces to numeric Infinity above and is + // intentionally stripped here, consistent with numeric Infinity.) delete opts.maxAge } else if (!isNaN(maxAge)) { opts.expires = new Date(Date.now() + maxAge) diff --git a/test/res.cookie.js b/test/res.cookie.js index 180d1be3452..253fe782872 100644 --- a/test/res.cookie.js +++ b/test/res.cookie.js @@ -183,6 +183,51 @@ describe('res', function(){ .get('/') .expect(500, /option maxAge is invalid/, done) }) + + it('should strip Infinity maxAge and produce a session cookie', function (done) { + var app = express() + + app.use(function (req, res) { + res.cookie('name', 'tobi', { maxAge: Infinity }) + res.end() + }) + + request(app) + .get('/') + .expect(200) + .expect('Set-Cookie', 'name=tobi; Path=/') + .end(done) + }) + + it('should strip -Infinity maxAge and produce a session cookie', function (done) { + var app = express() + + app.use(function (req, res) { + res.cookie('name', 'tobi', { maxAge: -Infinity }) + res.end() + }) + + request(app) + .get('/') + .expect(200) + .expect('Set-Cookie', 'name=tobi; Path=/') + .end(done) + }) + + it('should strip NaN maxAge and produce a session cookie', function (done) { + var app = express() + + app.use(function (req, res) { + res.cookie('name', 'tobi', { maxAge: NaN }) + res.end() + }) + + request(app) + .get('/') + .expect(200) + .expect('Set-Cookie', 'name=tobi; Path=/') + .end(done) + }) }) describe('priority', function () {