From 3963cfc0bd1dc1302175deaaa90597a9f94da59f Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 14 Aug 2025 15:18:15 +0200 Subject: [PATCH 1/3] 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 06758891b751d36e4282e9d6430f887840c5e93b Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Thu, 14 Aug 2025 15:20:54 +0200 Subject: [PATCH 2/3] 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 18c750773a3e39b89e3326e7de8eceb0d219d70d Mon Sep 17 00:00:00 2001 From: Ulises Gascon Date: Sat, 28 Mar 2026 12:01:23 +0100 Subject: [PATCH 3/3] 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 292b2024..a8b8180f 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]. @@ -1048,6 +1054,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