From 3b0c8dd8f7e8f8d929667fd7fb6cf9d0572d3a48 Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 14 Aug 2025 15:18:15 +0200 Subject: [PATCH 1/5] test: add specific RFC 6265 5.1.4 compliance tests --- test/session.js | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/test/session.js b/test/session.js index 46fed763..8266b221 100644 --- a/test/session.js +++ b/test/session.js @@ -2620,6 +2620,115 @@ describe('session()', function(){ }) }) +describe('path matching (RFC 6265)', function () { + describe('when "path" is "/" (root path)', function () { + before(function () { + this.server = createServer({ cookie: { path: '/' } }) + }) + + it('should set cookie when request-path is "/" (root path)', function (done) { + // RFC 6265 5.1.4: "The cookie-path and the request-path are identical." + request(this.server) + .get('/') + .expect(shouldSetCookie('connect.sid')) + .expect(200, done) + }) + + it('should set cookie when request-path is any path ("/foo")', function (done) { + // RFC 6265 5.1.4: "The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/")." + request(this.server) + .get('/foo') + .expect(shouldSetCookie('connect.sid')) + .expect(200, done) + }) + + it('should set cookie when request-path has multiple segments ("/foo/bar/baz")', function (done) { + // RFC 6265 5.1.4: "The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/")." + request(this.server) + .get('/foo/bar/baz') + .expect(shouldSetCookie('connect.sid')) + .expect(200, done) + }) + }) + + describe('when "path" is "/admin"', function () { + before(function () { + this.server = createServer({ cookie: { path: '/admin' } }) + }) + + it('should set cookie when request-path and cookie-path are identical ("/admin")', function (done) { + // RFC 6265 5.1.4: "The cookie-path and the request-path are identical." + request(this.server) + .get('/admin') + .expect(shouldSetCookie('connect.sid')) + .expect(200, done) + }) + + it('should set cookie when cookie-path is prefix and last char is "/" ("/admin/")', function (done) { + // RFC 6265 5.1.4: "The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/")." + request(this.server) + .get('/admin/') + .expect(shouldSetCookie('connect.sid')) + .expect(200, done) + }) + + it('should set cookie when cookie-path is prefix and next char is "/" ("/admin/users")', function (done) { + // RFC 6265 5.1.4: "The cookie-path is a prefix of the request-path, and the first + // character of the request-path that is not included in the cookie-path is a %x2F ("/") character." + request(this.server) + .get('/admin/users') + .expect(shouldSetCookie('connect.sid')) + .expect(200, done) + }) + + it('should NOT set cookie when cookie-path is not a prefix ("/administrator")', function (done) { + // RFC 6265 5.1.4: None of the path-match conditions are met + request(this.server) + .get('/administrator') + .expect(shouldNotHaveHeader('Set-Cookie')) + .expect(200, done) + }) + }) + + describe('when "path" is "/admin/" (trailing slash)', function () { + before(function () { + this.server = createServer({ cookie: { path: '/admin/' } }) + }) + + it('should set cookie when cookie-path is prefix and last char is "/" ("/admin/x")', function (done) { + // RFC 6265 5.1.4: "The cookie-path is a prefix of the request-path, and the last + // character of the cookie-path is %x2F ("/")." + request(this.server) + .get('/admin/x') + .expect(shouldSetCookie('connect.sid')) + .expect(200, done) + }) + + it('should NOT set cookie when request-path is not prefixed by cookie-path ("/admin")', function (done) { + // RFC 6265 5.1.4: cookie-path "/admin/" is not a prefix of request-path "/admin" + request(this.server) + .get('/admin') + .expect(shouldNotHaveHeader('Set-Cookie')) + .expect(200, done) + }) + + it('should NOT set cookie when cookie-path is not a prefix ("/administrator")', function (done) { + // RFC 6265 5.1.4: None of the path-match conditions are met: + // 1. The paths are not identical + // 2. "/admin/" is not a prefix of "/administrator" + // 3. The prefix condition with next character "/" is not applicable + request(this.server) + .get('/administrator') + .expect(shouldNotHaveHeader('Set-Cookie')) + .expect(200, done) + }) + }) +}) + + function cookie(res) { var setCookie = res.headers['set-cookie']; return (setCookie && setCookie[0]) || undefined; From 41ea858309740f71736825f9f1528b9a2cde9d0b Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 14 Aug 2025 15:20:54 +0200 Subject: [PATCH 2/5] feat: add specific RFC 6265 5.1.4 handler --- index.js | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 1a509318..366cdcd9 100644 --- a/index.js +++ b/index.js @@ -200,12 +200,15 @@ function session(options) { // pathname mismatch var originalPath = parseUrl.original(req).pathname || '/' var resolvedCookieOptions = typeof cookieOptions === 'function' ? cookieOptions(req) : cookieOptions - if (originalPath.indexOf(resolvedCookieOptions.path || '/') !== 0) { + var cfgPath = resolvedCookieOptions.path || '/' + + if (!rfcPathMatch(originalPath, cfgPath)) { debug('pathname mismatch') next() return } + // ensure a secret is available or bail if (!secret && !req.secret) { next(new Error('secret option required for sessions')); @@ -523,6 +526,42 @@ function session(options) { }; }; +/** + * Check if the cookiePath matches the requestPath following the + * rules in RFC 6265 section 5.1.4. + * + * @param {String} requestPath + * @param {String} cookiePath + * @return {Boolean} + * @private + */ + +function rfcPathMatch(requestPath, cookiePath) { + // Normalize inputs (Node 0.8-safe) + requestPath = (typeof requestPath === 'string' && requestPath.length) ? requestPath : '/'; + cookiePath = (typeof cookiePath === 'string' && cookiePath.length) ? cookiePath : '/'; + + // Root cookie matches everything + if (cookiePath === '/') return true; + + // Exact match + if (requestPath === cookiePath) return true; + + // Prefix match + if (requestPath.indexOf(cookiePath) === 0) { + // If cookiePath ends with '/', any longer requestPath is OK + if (cookiePath.charAt(cookiePath.length - 1) === '/') return true; + + // Otherwise the next char after the prefix must be '/' + var nextChar = requestPath.length > cookiePath.length + ? requestPath.charAt(cookiePath.length) + : ''; + return nextChar === '/'; + } + + return false; +} + /** * Generate a session ID for a new session. * From 2cf5fce0cf46dbd34aba9ec3e17f21e89d56d087 Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Sat, 28 Mar 2026 12:01:23 +0100 Subject: [PATCH 3/5] docs: note RFC 6265 5.1.4 compliance in cookie.path documentation --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 0a7edb04..162ea14d 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,12 @@ More information about can be found in [the proposal](https://github.com/privacy Specifies the value for the `Path` `Set-Cookie`. By default, this is set to `'/'`, which is the root path of the domain. +Since 1.19.1, path matching follows [RFC 6265 section 5.1.4][rfc-6265-5.1.4]. This means +the session middleware will only activate when the request path is an exact match or falls +under a segment boundary of the cookie path. For example, a cookie path of `/admin` will +match `/admin` and `/admin/users` but will **not** match `/administrator`. Prior versions +used a simple prefix check that did not enforce segment boundaries. + ##### cookie.priority Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1]. @@ -1038,6 +1044,7 @@ On Windows, use the corresponding command; [MIT](LICENSE) +[rfc-6265-5.1.4]: https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4 [rfc-6265bis-03-4.1.2.7]: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7 [rfc-cutler-httpbis-partitioned-cookies]: https://tools.ietf.org/html/draft-cutler-httpbis-partitioned-cookies/ [rfc-west-cookie-priority-00-4.1]: https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1 From 5f638e908ba5bad5e476c24a777889226556eadd Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 5 Jul 2026 20:58:38 -0500 Subject: [PATCH 4/5] feat: enhance cookie handling for RFC 6265 compliance with path matching and auto secure/sameSite options --- index.js | 7 ++++--- test/session.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 366cdcd9..41f9447f 100644 --- a/index.js +++ b/index.js @@ -158,15 +158,16 @@ function session(options) { store.generate = function(req){ req.sessionID = generateId(req); req.session = new Session(req); - req.session.cookie = new Cookie(typeof cookieOptions === 'function' ? cookieOptions(req) : cookieOptions); + var resolvedCookieOptions = typeof cookieOptions === 'function' ? cookieOptions(req) : cookieOptions; + req.session.cookie = new Cookie(resolvedCookieOptions); var isSecure = issecure(req, trustProxy); - if (cookieOptions.secure === 'auto') { + if (resolvedCookieOptions.secure === 'auto') { req.session.cookie.secure = isSecure; } - if (cookieOptions.sameSite === 'auto') { + if (resolvedCookieOptions.sameSite === 'auto') { req.session.cookie.sameSite = isSecure ? 'none' : 'lax'; } }; diff --git a/test/session.js b/test/session.js index 8266b221..330c4137 100644 --- a/test/session.js +++ b/test/session.js @@ -853,6 +853,40 @@ describe('session()', function(){ done(); }); }); + + it('should not set cookie when request-path does not match returned path', function (done) { + var cookieCallback = function () { + return { path: '/admin', httpOnly: true, secure: false }; + }; + var server = createServer({ cookie: cookieCallback }); + request(server) + .get('/administrator') + .expect(shouldNotHaveHeader('Set-Cookie')) + .expect(200, done); + }); + + it('should resolve "secure" and "sameSite" set to "auto"', function (done) { + function setup (req) { + req.secure = JSON.parse(req.headers['x-secure']) + } + + function respond (req, res) { + res.end(String(req.secure)) + } + + var cookieCallback = function () { + return { path: '/', secure: 'auto', sameSite: 'auto' }; + }; + var server = createServer(setup, { cookie: cookieCallback }, respond); + request(server) + .get('/') + .set('X-Secure', 'true') + .expect(shouldSetCookieWithAttribute('connect.sid', 'Secure')) + .expect( + shouldSetCookieWithAttributeAndValue('connect.sid', 'SameSite', 'None') + ) + .expect(200, 'true', done); + }); }); describe('when "sameSite" set to "auto"', function () { describe('basic functionality', function () { From 187c62d861e3e454bf15892e070c5ab9e9df7280 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Sun, 5 Jul 2026 21:04:05 -0500 Subject: [PATCH 5/5] feat: enforce RFC 6265 section 5.1.4 path matching for session cookie path --- HISTORY.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index fae1a0f1..251d5fa7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,9 @@ +unreleased +========== + + * Enforce RFC 6265 section 5.1.4 path matching for the session cookie path + * Fix `secure: 'auto'` and `sameSite: 'auto'` not being resolved when `cookie` is a function + 1.19.0 / 2026-01-22 ==========