From 38934f0e7ffd152916d21a582a420d7c2fef962b Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 3 Sep 2026 00:18:51 +0100 Subject: [PATCH 1/2] fix: parse JSON cookies with falsy values --- index.js | 2 +- test/cookieParser.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index dd6d479..ed9db4e 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,7 @@ function JSONCookies (obj) { key = cookies[i] val = JSONCookie(obj[key]) - if (val) { + if (val !== undefined) { obj[key] = val } } diff --git a/test/cookieParser.js b/test/cookieParser.js index 6031823..ab42aa9 100644 --- a/test/cookieParser.js +++ b/test/cookieParser.js @@ -162,6 +162,21 @@ describe('cookieParser.JSONCookie(str)', function () { }) }) +describe('cookieParser.JSONCookies(obj)', function () { + it('should parse JSON cookie values, including falsy ones', function () { + assert.deepEqual(cookieParser.JSONCookies({ + f: 'j:false', + z: 'j:0', + e: 'j:""', + n: 'j:null' + }), { f: false, z: 0, e: '', n: null }) + }) + + it('should leave non-JSON cookie values untouched', function () { + assert.deepEqual(cookieParser.JSONCookies({ a: 'bar', b: 'j:{foo:"bar"}' }), { a: 'bar', b: 'j:{foo:"bar"}' }) + }) +}) + describe('cookieParser.signedCookie(str, secret)', function () { it('should return undefined for non-string arguments', function () { assert.strictEqual(cookieParser.signedCookie(undefined, 'keyboard cat'), undefined) From c2f69132c86faf670720e24564d2e3f66407e149 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Mon, 7 Sep 2026 15:02:44 +0100 Subject: [PATCH 2/2] fix: keep boolean false reserved for the signature-failure marker Parsing "j:false" into boolean false made an authentically signed value indistinguishable from a cookie whose signature did not verify, since signedCookies() marks those with false (#168). The other falsy JSON values have no such conflict and are still parsed. Co-Authored-By: Claude Opus 5 (1M context) --- index.js | 6 +++++- test/cookieParser.js | 26 +++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index ed9db4e..1c934da 100644 --- a/index.js +++ b/index.js @@ -109,7 +109,11 @@ function JSONCookies (obj) { key = cookies[i] val = JSONCookie(obj[key]) - if (val !== undefined) { + // Boolean false is reserved: signedCookies() uses it to mark a cookie whose + // signature did not verify, so writing a parsed "j:false" here would make an + // authentic value indistinguishable from a tampered one (#168). Left as the + // raw string until that representation is settled. + if (val !== undefined && val !== false) { obj[key] = val } } diff --git a/test/cookieParser.js b/test/cookieParser.js index ab42aa9..6226f59 100644 --- a/test/cookieParser.js +++ b/test/cookieParser.js @@ -81,6 +81,23 @@ describe('cookieParser()', function () { .expect(200, '{"foo":"foobarbaz"}', done) }) + it('should keep an authentic JSON false distinct from a tampered cookie', function (done) { + var signed = signature.sign('j:false', 'keyboard cat') + request(createServer('keyboard cat')) + .get('/signed') + .set('Cookie', 'flag=s:' + signed) + .expect(200, '{"flag":"j:false"}', done) + }) + + it('should report a tampered cookie as false', function (done) { + var signed = signature.sign('j:false', 'keyboard cat') + var tampered = signed.slice(0, -1) + (signed.slice(-1) === 'a' ? 'b' : 'a') + request(createServer('keyboard cat')) + .get('/signed') + .set('Cookie', 'flag=s:' + tampered) + .expect(200, '{"flag":false}', done) + }) + it('should remove the signed value from req.cookies', function (done) { request(createServer('keyboard cat')) .get('/') @@ -163,13 +180,16 @@ describe('cookieParser.JSONCookie(str)', function () { }) describe('cookieParser.JSONCookies(obj)', function () { - it('should parse JSON cookie values, including falsy ones', function () { + it('should parse falsy JSON cookie values', function () { assert.deepEqual(cookieParser.JSONCookies({ - f: 'j:false', z: 'j:0', e: 'j:""', n: 'j:null' - }), { f: false, z: 0, e: '', n: null }) + }), { z: 0, e: '', n: null }) + }) + + it('should leave a JSON false as its raw string, so it stays distinct from the signature-failure marker', function () { + assert.deepEqual(cookieParser.JSONCookies({ f: 'j:false' }), { f: 'j:false' }) }) it('should leave non-JSON cookie values untouched', function () {