From 06c6b88808f6d836afc58296812235a96d708b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulises=20Gasc=C3=B3n?= Date: Sun, 10 Mar 2024 20:04:02 +0100 Subject: [PATCH 01/13] docs: update release date --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 0784045557..b674cab4b0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,4 @@ -4.18.3 / 2024-02-26 +4.18.3 / 2024-02-29 ========== * Fix routing requests without method From 414854b82ea4312f50641ddf7668c9194c3c209c Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Thu, 29 Feb 2024 08:49:34 -0600 Subject: [PATCH 02/13] docs: nominating @wesleytodd to be project captian --- Contributing.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/Contributing.md b/Contributing.md index dfe5f13833..7311454dd8 100644 --- a/Contributing.md +++ b/Contributing.md @@ -154,7 +154,21 @@ dissent. When the PR is merged, a TC member will add them to the proper GitHub/ ### Current Project Captains -- `expressjs.com`: @crandmck -- `multer`: @LinusU -- `path-to-regexp`: @blakeembrey -- `router`: @dougwilson +- `expressjs/express`: @wesleytodd +- `expressjs/discussions`: @wesleytodd +- `expressjs/expressjs.com`: @crandmck +- `expressjs/body-parser`: @wesleytodd +- `expressjs/multer`: @LinusU +- `expressjs/cookie-parser`: @wesleytodd +- `expressjs/generator`: @wesleytodd +- `expressjs/statusboard`: @wesleytodd +- `pillarjs/path-to-regexp`: @blakeembrey +- `pillarjs/router`: @dougwilson, @wesleytodd +- `pillarjs/finalhandler`: @wesleytodd +- `pillarjs/request`: @wesleytodd +- `jshttp/http-errors`: @wesleytodd +- `jshttp/cookie`: @wesleytodd +- `jshttp/on-finished`: @wesleytodd +- `jshttp/forwarded`: @wesleytodd +- `jshttp/proxy-addr`: @wesleytodd + From 4ee853e837dcc6c6c9f93c52278abe775c717fa1 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Wed, 28 Feb 2024 14:49:11 -0600 Subject: [PATCH 03/13] docs: loosen TC activity rules --- Contributing.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Contributing.md b/Contributing.md index 7311454dd8..a9ba84690c 100644 --- a/Contributing.md +++ b/Contributing.md @@ -118,11 +118,13 @@ nominate someone to take their place. TC members will be added as admin's on the Github orgs, npm orgs, and other resources as necessary to be effective in the role. -To remain "active" a TC member should have participation within the last 6 months and miss -no more than three consecutive TC meetings. Members who do not meet this are expected to step down. -If A TC member does not step down, an issue can be opened in the discussions repo to move them -to inactive status. TC members who step down or are removed due to inactivity will be moved -into inactive status. +To remain "active" a TC member should have participation within the last 12 months and miss +no more than six consecutive TC meetings. Our goal is to increase participation, not punish +people for any lack of participation, this guideline should be only be used as such +(replace an inactive member with a new active one, for example). Members who do not meet this +are expected to step down. If A TC member does not step down, an issue can be opened in the +discussions repo to move them to inactive status. TC members who step down or are removed due +to inactivity will be moved into inactive status. Inactive status members can become active members by self nomination if the TC is not already larger than the maximum of 10. They will also be given preference if, while at max size, an From 69a4cf2819c4449ec6ea45649691fb43a528d5d1 Mon Sep 17 00:00:00 2001 From: Rich Hodgkins Date: Thu, 11 Jan 2024 11:36:11 +0000 Subject: [PATCH 04/13] deps: cookie@0.6.0 closes #5404 --- History.md | 7 +++++++ package.json | 2 +- test/res.cookie.js | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index b674cab4b0..0559bb012c 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +unreleased +========== + + * deps: cookie@0.6.0 + 4.18.3 / 2024-02-29 ========== @@ -6,6 +11,8 @@ - Fix strict json error message on Node.js 19+ - deps: content-type@~1.0.5 - deps: raw-body@2.5.2 + * deps: cookie@0.6.0 + - Add `partitioned` option 4.18.2 / 2022-10-08 =================== diff --git a/package.json b/package.json index c3845d2d4b..990e98dc1a 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", diff --git a/test/res.cookie.js b/test/res.cookie.js index 93deb76988..c837820605 100644 --- a/test/res.cookie.js +++ b/test/res.cookie.js @@ -82,6 +82,22 @@ describe('res', function(){ }) }) + describe('partitioned', function () { + it('should set partitioned', function (done) { + var app = express(); + + app.use(function (req, res) { + res.cookie('name', 'tobi', { partitioned: true }); + res.end(); + }); + + request(app) + .get('/') + .expect('Set-Cookie', 'name=tobi; Path=/; Partitioned') + .expect(200, done) + }) + }) + describe('maxAge', function(){ it('should set relative expires', function(done){ var app = express(); From 567c9c665d0de4c344b8e160146050770233783c Mon Sep 17 00:00:00 2001 From: Rand McKinney Date: Sat, 16 Mar 2024 11:57:42 -0600 Subject: [PATCH 05/13] Add note on how to update docs for new release (#5541) * Update Release-Process.md Add note about updating docs. * Update Release-Process.md * Update Release-Process.md --- Release-Process.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Release-Process.md b/Release-Process.md index ae740972f7..55e6218925 100644 --- a/Release-Process.md +++ b/Release-Process.md @@ -184,3 +184,9 @@ $ npm publish **NOTE:** The version number to publish will be picked up automatically from package.json. + +### Step 7. Update documentation website + +The documentation website https://expressjs.com/ documents the current release version in various places. For a new release: +1. Change the value of `current_version` in https://github.com/expressjs/expressjs.com/blob/gh-pages/_data/express.yml to match the latest version number. +2. Add a new section to the change log. For example, for a 4.x release, https://github.com/expressjs/expressjs.com/blob/gh-pages/en/changelog/4x.md, From 0867302ddbde0e9463d0564fea5861feb708c2dd Mon Sep 17 00:00:00 2001 From: FDrag0n <34733637+FDrag0n@users.noreply.github.com> Date: Fri, 15 Mar 2024 15:49:01 +0800 Subject: [PATCH 06/13] Prevent open redirect allow list bypass due to encodeurl Co-authored-by: Jon Church --- lib/response.js | 20 ++++++++++++++++++- test/res.location.js | 46 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/lib/response.js b/lib/response.js index fede486c06..f7c94d10e7 100644 --- a/lib/response.js +++ b/lib/response.js @@ -34,6 +34,7 @@ var extname = path.extname; var mime = send.mime; var resolve = path.resolve; var vary = require('vary'); +var urlParse = require('url').parse; /** * Response prototype. @@ -911,8 +912,25 @@ res.location = function location(url) { loc = this.req.get('Referrer') || '/'; } + var lowerLoc = loc.toLowerCase(); + var encodedUrl = encodeUrl(loc); + if (lowerLoc.indexOf('https://') === 0 || lowerLoc.indexOf('http://') === 0) { + try { + var parsedUrl = urlParse(loc); + var parsedEncodedUrl = urlParse(encodedUrl); + // Because this can encode the host, check that we did not change the host + if (parsedUrl.host !== parsedEncodedUrl.host) { + // If the host changes after encodeUrl, return the original url + return this.set('Location', loc); + } + } catch (e) { + // If parse fails, return the original url + return this.set('Location', loc); + } + } + // set location - return this.set('Location', encodeUrl(loc)); + return this.set('Location', encodedUrl); }; /** diff --git a/test/res.location.js b/test/res.location.js index 158afac01e..38cab027a8 100644 --- a/test/res.location.js +++ b/test/res.location.js @@ -1,13 +1,27 @@ 'use strict' var express = require('../') - , request = require('supertest'); + , request = require('supertest') + , url = require('url'); describe('res', function(){ describe('.location(url)', function(){ it('should set the header', function(done){ var app = express(); + app.use(function(req, res){ + res.location('http://google.com/').end(); + }); + + request(app) + .get('/') + .expect('Location', 'http://google.com/') + .expect(200, done) + }) + + it('should preserve trailing slashes when not present', function(done){ + var app = express(); + app.use(function(req, res){ res.location('http://google.com').end(); }); @@ -31,6 +45,36 @@ describe('res', function(){ .expect(200, done) }) + it('should not encode bad "url"', function (done) { + var app = express() + + app.use(function (req, res) { + // This is here to show a basic check one might do which + // would pass but then the location header would still be bad + if (url.parse(req.query.q).host !== 'google.com') { + res.status(400).end('Bad url'); + } + res.location(req.query.q).end(); + }); + + request(app) + .get('/?q=http://google.com\\@apple.com') + .expect(200) + .expect('Location', 'http://google.com\\@apple.com') + .end(function (err) { + if (err) { + throw err; + } + + // This ensures that our protocol check is case insensitive + request(app) + .get('/?q=HTTP://google.com\\@apple.com') + .expect(200) + .expect('Location', 'HTTP://google.com\\@apple.com') + .end(done) + }); + }); + it('should not touch already-encoded sequences in "url"', function (done) { var app = express() From 084e36506a18774f85206a65d8da04dc1107fc1b Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Wed, 20 Mar 2024 10:09:10 -0500 Subject: [PATCH 07/13] 4.19.0 --- History.md | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 0559bb012c..877f1c56fb 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,7 @@ -unreleased +4.18.3 / 2024-03-20 ========== + * Prevent open redirect allow list bypass due to encodeurl * deps: cookie@0.6.0 4.18.3 / 2024-02-29 diff --git a/package.json b/package.json index 990e98dc1a..eca07b57c8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express", "description": "Fast, unopinionated, minimalist web framework", - "version": "4.18.3", + "version": "4.19.0", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ", From 11f2b1db227fd42c2508c427032c1ec671b306be Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Wed, 20 Mar 2024 11:33:31 -0500 Subject: [PATCH 08/13] build: fix build due to inconsistent supertest behavior in older versions --- test/res.location.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/res.location.js b/test/res.location.js index 38cab027a8..71fbaec03d 100644 --- a/test/res.location.js +++ b/test/res.location.js @@ -58,7 +58,7 @@ describe('res', function(){ }); request(app) - .get('/?q=http://google.com\\@apple.com') + .get('/?q=http://google.com' + encodeURIComponent('\\@apple.com')) .expect(200) .expect('Location', 'http://google.com\\@apple.com') .end(function (err) { @@ -68,7 +68,7 @@ describe('res', function(){ // This ensures that our protocol check is case insensitive request(app) - .get('/?q=HTTP://google.com\\@apple.com') + .get('/?q=HTTP://google.com' + encodeURIComponent('\\@apple.com')) .expect(200) .expect('Location', 'HTTP://google.com\\@apple.com') .end(done) From a1fa90fcea7d8e844e1c9938ad095d62669c3abd Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Wed, 20 Mar 2024 16:39:46 -0500 Subject: [PATCH 09/13] fixed un-edited version in history.md for 4.19.0 --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 877f1c56fb..b6208ea7e3 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,4 @@ -4.18.3 / 2024-03-20 +4.19.0 / 2024-03-20 ========== * Prevent open redirect allow list bypass due to encodeurl From a003cfab034fbadb1c78ae337ee8ab389adda217 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Wed, 20 Mar 2024 16:39:46 -0500 Subject: [PATCH 10/13] Allow passing non-strings to res.location with new encoding handling checks fixes #5554 #5555 --- History.md | 5 +++++ lib/response.js | 2 +- test/res.location.js | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index b6208ea7e3..2fda95c155 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +unreleased changes +========== + + * Allow passing non-strings to res.location with new encoding handling checks + 4.19.0 / 2024-03-20 ========== diff --git a/lib/response.js b/lib/response.js index f7c94d10e7..6fddbf3516 100644 --- a/lib/response.js +++ b/lib/response.js @@ -905,7 +905,7 @@ res.cookie = function (name, value, options) { */ res.location = function location(url) { - var loc = url; + var loc = String(url); // "back" is an alias for the referrer if (url === 'back') { diff --git a/test/res.location.js b/test/res.location.js index 71fbaec03d..d1bbf4b687 100644 --- a/test/res.location.js +++ b/test/res.location.js @@ -145,5 +145,20 @@ describe('res', function(){ .expect(200, done) }) }) + + if (typeof URL !== 'undefined') { + it('should accept an instance of URL', function (done) { + var app = express(); + + app.use(function(req, res){ + res.location(new URL('http://google.com/')).end(); + }); + + request(app) + .get('/') + .expect('Location', 'http://google.com/') + .expect(200, done); + }); + } }) }) From 4f0f6cc67d531431c096ea006c2191b92931bbc3 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Wed, 20 Mar 2024 17:17:59 -0500 Subject: [PATCH 11/13] 4.19.1 --- History.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 2fda95c155..c4cdd94dac 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,4 @@ -unreleased changes +4.19.1 / 2024-03-20 ========== * Allow passing non-strings to res.location with new encoding handling checks diff --git a/package.json b/package.json index eca07b57c8..51c6aba212 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express", "description": "Fast, unopinionated, minimalist web framework", - "version": "4.19.0", + "version": "4.19.1", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ", From da4d763ff6ba9df6dbd8f1f0b1d05412dda934d5 Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Thu, 21 Mar 2024 12:13:56 -0500 Subject: [PATCH 12/13] Improved fix for open redirect allow list bypass Co-authored-by: Jon Church Co-authored-by: Blake Embrey --- History.md | 5 + lib/response.js | 31 ++--- test/res.location.js | 307 +++++++++++++++++++++++++++++++++++++------ 3 files changed, 280 insertions(+), 63 deletions(-) diff --git a/History.md b/History.md index c4cdd94dac..f62574a7ee 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +unreleased +========== + + * Improved fix for open redirect allow list bypass + 4.19.1 / 2024-03-20 ========== diff --git a/lib/response.js b/lib/response.js index 6fddbf3516..dd7b3c8201 100644 --- a/lib/response.js +++ b/lib/response.js @@ -34,7 +34,6 @@ var extname = path.extname; var mime = send.mime; var resolve = path.resolve; var vary = require('vary'); -var urlParse = require('url').parse; /** * Response prototype. @@ -56,6 +55,7 @@ module.exports = res */ var charsetRegExp = /;\s*charset\s*=/; +var schemaAndHostRegExp = /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?\/\/[^\\\/\?]+/; /** * Set status `code`. @@ -905,32 +905,23 @@ res.cookie = function (name, value, options) { */ res.location = function location(url) { - var loc = String(url); + var loc; // "back" is an alias for the referrer if (url === 'back') { loc = this.req.get('Referrer') || '/'; + } else { + loc = String(url); } - var lowerLoc = loc.toLowerCase(); - var encodedUrl = encodeUrl(loc); - if (lowerLoc.indexOf('https://') === 0 || lowerLoc.indexOf('http://') === 0) { - try { - var parsedUrl = urlParse(loc); - var parsedEncodedUrl = urlParse(encodedUrl); - // Because this can encode the host, check that we did not change the host - if (parsedUrl.host !== parsedEncodedUrl.host) { - // If the host changes after encodeUrl, return the original url - return this.set('Location', loc); - } - } catch (e) { - // If parse fails, return the original url - return this.set('Location', loc); - } - } + var m = schemaAndHostRegExp.exec(loc); + var pos = m ? m[0].length + 1 : 0; + + // Only encode after host to avoid invalid encoding which can introduce + // vulnerabilities (e.g. `\\` to `%5C`). + loc = loc.slice(0, pos) + encodeUrl(loc.slice(pos)); - // set location - return this.set('Location', encodedUrl); + return this.set('Location', loc); }; /** diff --git a/test/res.location.js b/test/res.location.js index d1bbf4b687..c80b38de6b 100644 --- a/test/res.location.js +++ b/test/res.location.js @@ -2,6 +2,7 @@ var express = require('../') , request = require('supertest') + , assert = require('assert') , url = require('url'); describe('res', function(){ @@ -45,49 +46,6 @@ describe('res', function(){ .expect(200, done) }) - it('should not encode bad "url"', function (done) { - var app = express() - - app.use(function (req, res) { - // This is here to show a basic check one might do which - // would pass but then the location header would still be bad - if (url.parse(req.query.q).host !== 'google.com') { - res.status(400).end('Bad url'); - } - res.location(req.query.q).end(); - }); - - request(app) - .get('/?q=http://google.com' + encodeURIComponent('\\@apple.com')) - .expect(200) - .expect('Location', 'http://google.com\\@apple.com') - .end(function (err) { - if (err) { - throw err; - } - - // This ensures that our protocol check is case insensitive - request(app) - .get('/?q=HTTP://google.com' + encodeURIComponent('\\@apple.com')) - .expect(200) - .expect('Location', 'HTTP://google.com\\@apple.com') - .end(done) - }); - }); - - it('should not touch already-encoded sequences in "url"', function (done) { - var app = express() - - app.use(function (req, res) { - res.location('https://google.com?q=%A710').end() - }) - - request(app) - .get('/') - .expect('Location', 'https://google.com?q=%A710') - .expect(200, done) - }) - describe('when url is "back"', function () { it('should set location from "Referer" header', function (done) { var app = express() @@ -146,6 +104,79 @@ describe('res', function(){ }) }) + it('should encode data uri', function (done) { + var app = express() + app.use(function (req, res) { + res.location('data:text/javascript,export default () => { }').end(); + }); + + request(app) + .get('/') + .expect('Location', 'data:text/javascript,export%20default%20()%20=%3E%20%7B%20%7D') + .expect(200, done) + }) + + it('should encode data uri', function (done) { + var app = express() + app.use(function (req, res) { + res.location('data:text/javascript,export default () => { }').end(); + }); + + request(app) + .get('/') + .expect('Location', 'data:text/javascript,export%20default%20()%20=%3E%20%7B%20%7D') + .expect(200, done) + }) + + it('should consistently handle non-string input: boolean', function (done) { + var app = express() + app.use(function (req, res) { + res.location(true).end(); + }); + + request(app) + .get('/') + .expect('Location', 'true') + .expect(200, done) + }); + + it('should consistently handle non-string inputs: object', function (done) { + var app = express() + app.use(function (req, res) { + res.location({}).end(); + }); + + request(app) + .get('/') + .expect('Location', '[object%20Object]') + .expect(200, done) + }); + + it('should consistently handle non-string inputs: array', function (done) { + var app = express() + app.use(function (req, res) { + res.location([]).end(); + }); + + request(app) + .get('/') + .expect('Location', '') + .expect(200, done) + }); + + it('should consistently handle empty string input', function (done) { + var app = express() + app.use(function (req, res) { + res.location('').end(); + }); + + request(app) + .get('/') + .expect('Location', '') + .expect(200, done) + }); + + if (typeof URL !== 'undefined') { it('should accept an instance of URL', function (done) { var app = express(); @@ -161,4 +192,194 @@ describe('res', function(){ }); } }) + + describe('location header encoding', function() { + function createRedirectServerForDomain (domain) { + var app = express(); + app.use(function (req, res) { + var host = url.parse(req.query.q, false, true).host; + // This is here to show a basic check one might do which + // would pass but then the location header would still be bad + if (host !== domain) { + res.status(400).end('Bad host: ' + host + ' !== ' + domain); + } + res.location(req.query.q).end(); + }); + return app; + } + + function testRequestedRedirect (app, inputUrl, expected, expectedHost, done) { + return request(app) + // Encode uri because old supertest does not and is required + // to test older node versions. New supertest doesn't re-encode + // so this works in both. + .get('/?q=' + encodeURIComponent(inputUrl)) + .expect('') // No body. + .expect(200) + .expect('Location', expected) + .end(function (err, res) { + if (err) { + console.log('headers:', res.headers) + console.error('error', res.error, err); + return done(err, res); + } + + // Parse the hosts from the input URL and the Location header + var inputHost = url.parse(inputUrl, false, true).host; + var locationHost = url.parse(res.headers['location'], false, true).host; + + assert.strictEqual(locationHost, expectedHost); + + // Assert that the hosts are the same + if (inputHost !== locationHost) { + return done(new Error('Hosts do not match: ' + inputHost + " !== " + locationHost)); + } + + return done(null, res); + }); + } + + it('should not touch already-encoded sequences in "url"', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'https://google.com?q=%A710', + 'https://google.com?q=%A710', + 'google.com', + done + ); + }); + + it('should consistently handle relative urls', function (done) { + var app = createRedirectServerForDomain(null); + testRequestedRedirect( + app, + '/foo/bar', + '/foo/bar', + null, + done + ); + }); + + it('should not encode urls in such a way that they can bypass redirect allow lists', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'http://google.com\\@apple.com', + 'http://google.com\\@apple.com', + 'google.com', + done + ); + }); + + it('should not be case sensitive', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'HTTP://google.com\\@apple.com', + 'HTTP://google.com\\@apple.com', + 'google.com', + done + ); + }); + + it('should work with https', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'https://google.com\\@apple.com', + 'https://google.com\\@apple.com', + 'google.com', + done + ); + }); + + it('should correctly encode schemaless paths', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + '//google.com\\@apple.com/', + '//google.com\\@apple.com/', + 'google.com', + done + ); + }); + + it('should percent encode backslashes in the path', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'https://google.com/foo\\bar\\baz', + 'https://google.com/foo%5Cbar%5Cbaz', + 'google.com', + done + ); + }); + + it('should encode backslashes in the path after the first backslash that triggered path parsing', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'https://google.com\\@app\\l\\e.com', + 'https://google.com\\@app%5Cl%5Ce.com', + 'google.com', + done + ); + }); + + it('should escape header splitting for old node versions', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'http://google.com\\@apple.com/%0d%0afoo:%20bar', + 'http://google.com\\@apple.com/%0d%0afoo:%20bar', + 'google.com', + done + ); + }); + + it('should encode unicode correctly', function (done) { + var app = createRedirectServerForDomain(null); + testRequestedRedirect( + app, + '/%e2%98%83', + '/%e2%98%83', + null, + done + ); + }); + + it('should encode unicode correctly even with a bad host', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'http://google.com\\@apple.com/%e2%98%83', + 'http://google.com\\@apple.com/%e2%98%83', + 'google.com', + done + ); + }); + + it('should work correctly despite using deprecated url.parse', function (done) { + var app = createRedirectServerForDomain('google.com'); + testRequestedRedirect( + app, + 'https://google.com\'.bb.com/1.html', + 'https://google.com\'.bb.com/1.html', + 'google.com', + done + ); + }); + + it('should encode file uri path', function (done) { + var app = createRedirectServerForDomain(''); + testRequestedRedirect( + app, + 'file:///etc\\passwd', + 'file:///etc%5Cpasswd', + '', + done + ); + }); + }); }) From 04bc62787be974874bc1467b23606c36bc9779ba Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Mon, 25 Mar 2024 09:26:03 -0500 Subject: [PATCH 13/13] 4.19.2 --- History.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index f62574a7ee..ac2e7cf719 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,4 @@ -unreleased +4.19.2 / 2024-03-25 ========== * Improved fix for open redirect allow list bypass diff --git a/package.json b/package.json index 51c6aba212..f299d882b0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express", "description": "Fast, unopinionated, minimalist web framework", - "version": "4.19.1", + "version": "4.19.2", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ",