Compare commits

...

23 Commits
5.x ... 4.22.0

Author SHA1 Message Date
Ulises Gascón
49744abd11 4.22.0 (#6921) 2025-12-01 17:17:12 +01:00
Chris de Almeida
6e97452f60 sec: security patch for CVE-2024-51999 2025-12-01 17:14:33 +01:00
Phillip Barta
6a23d34d65 deps: use tilde notation for qs (#6919) 2025-11-25 16:17:24 -05:00
Ulises Gascón
8c12cdf93b deps: qs@6.14.0 (#6909) 2025-11-21 15:22:59 +01:00
Ulises Gascón
7fea74fcf0 deps: use tilde notation for certain dependencies (#6905) 2025-11-20 20:34:22 +01:00
Jon Church
dac7a0475a chore: wider range for query test skip (#6513) 2025-05-15 11:40:07 -05:00
Phillip Barta
997919b488 ci: add node.js 24 to test matrix (#6506) 2025-05-10 18:52:25 -05:00
Phillip Barta
36fb59c6c7 fix(ci): reorder npm i steps to fix ci for older node versions (#6336) 2025-02-17 10:53:01 -06:00
Phillip Barta
3a5edfaff0 fix(ci): updated github actions ci workflow (#6323)
Backport of #6314
2025-02-12 10:25:08 -06:00
Wes Todd
52d978119a fix(test): add test for method routes without paths #5955 2025-01-15 18:22:08 -06:00
Ulises Gascon
fe93005e04 ci: add support for Node.js@23.0
Co-authored-by: Sebastian Beltran <bjohansebas@gmail.com>
Original PR: https://github.com/expressjs/express/pull/6075
2025-01-08 14:53:41 -06:00
Shahan Arshad
20415843f4 fix(examples): improve readability of user assignment (#6190)
Edit assignment inside condition check to a separate step
2024-12-17 09:59:55 -06:00
Ulises Gascón
1faf228935 4.21.2
Signed-off-by: Ulises Gascon <ulisesgascongonzalez@gmail.com>
2024-12-05 17:27:56 -05:00
Jon Church
2e0fb646d0 deps: bump path-to-regexp@0.1.12 (#6209)
fix backtracking protection
2024-12-05 17:16:48 -05:00
Blake Embrey
59fc27028e deps: path-to-regexp@0.1.11 (#5956)
Co-authored-by: Ulises Gascón <ulisesgascongonzalez@gmail.com>
2024-10-20 20:09:32 +02:00
Sebastian Beltran
51fc39ccf8 docs: add funding (#6065) 2024-10-20 19:58:25 +02:00
Ulises Gascón
8e229f9275 4.21.1
PR-URL: https://github.com/expressjs/express/pull/6031
2024-10-08 20:36:08 +02:00
Josh Buker
a024c8a7b6 fix(deps): cookie@0.7.1
Co-authored-by: Ulises Gascón <ulisesgascongonzalez@gmail.com>
2024-10-08 12:13:25 +02:00
Wes Todd
7e562c6d8d 4.21.0 2024-09-11 17:32:14 -05:00
agadzinski93
1bcde96bc8 fix(deps): qs@6.13.0 (#5946)
Co-authored-by: Wes Todd <wes@wesleytodd.com>
2024-09-11 17:27:37 -05:00
Wes Todd
7d36477568 fix(deps): serve-static@1.16.2 (#5951) 2024-09-11 17:26:00 -05:00
Wes Todd
40d2d8f2c8 fix(deps): finalhandler@1.3.1 2024-09-11 17:20:33 -05:00
Blake Embrey
77ada906db Deprecate "back" magic string in redirects (#5935) 2024-09-11 12:24:22 -07:00
9 changed files with 215 additions and 64 deletions

View File

@@ -13,6 +13,9 @@ on:
paths-ignore:
- '*.md'
permissions:
contents: read
# Cancel in progress workflows
# in the scenario where we already had a run going for that PR/branch/tag but then triggered a new run
concurrency:
@@ -25,14 +28,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js {{ matrix.node-version }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
persist-credentials: false
- name: Install dependencies
run: npm install --ignore-scripts --only=dev
run: npm install --ignore-scripts --include=dev
- name: Run lint
run: npm run lint
@@ -65,6 +67,8 @@ jobs:
- "20"
- "21"
- "22"
- "23"
- "24"
# Use supported versions of our testing tools under older versions of Node
# Install npm in some specific cases where we need to
include:
@@ -93,22 +97,25 @@ jobs:
npm-i: "mocha@6.2.2 nyc@14.1.1 supertest@6.1.6"
- node-version: "8"
npm-i: "mocha@7.2.0 nyc@14.1.1"
npm-i: "mocha@7.2.0 nyc@14.1.1 supertest@6.1.6"
- node-version: "9"
npm-i: "mocha@7.2.0 nyc@14.1.1"
npm-i: "mocha@7.2.0 nyc@14.1.1 supertest@6.1.6"
- node-version: "10"
npm-i: "mocha@8.4.0"
npm-i: "mocha@8.4.0 supertest@6.1.6"
- node-version: "11"
npm-i: "mocha@8.4.0"
npm-i: "mocha@8.4.0 supertest@6.1.6"
- node-version: "12"
npm-i: "mocha@9.2.2"
npm-i: "mocha@9.2.2 supertest@6.1.6"
- node-version: "13"
npm-i: "mocha@9.2.2"
npm-i: "mocha@9.2.2 supertest@6.1.6"
- node-version: "15"
npm-i: "supertest@6.1.6"
runs-on: ${{ matrix.os }}
steps:
@@ -130,13 +137,13 @@ jobs:
npm config set loglevel error
shell: bash
- name: Install dependencies
run: npm install
- name: Install Node version specific dev deps
if: ${{ matrix.npm-i != '' }}
run: npm install --save-dev ${{ matrix.npm-i }}
- name: Install dependencies
run: npm install
- name: Remove non-test dependencies
run: npm rm --silent --save-dev connect-redis
@@ -147,44 +154,39 @@ jobs:
- name: Run tests
shell: bash
run: |
npm run test-ci
cp coverage/lcov.info "coverage/${{ matrix.node-version }}.lcov"
- name: Collect code coverage
run: |
mv ./coverage "./${{ matrix.node-version }}"
mkdir ./coverage
mv "./${{ matrix.node-version }}" "./coverage/${{ matrix.node-version }}"
run: npm run test-ci
- name: Upload code coverage
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: coverage
path: ./coverage
name: coverage-node-${{ matrix.node-version }}-${{ matrix.os }}
path: ./coverage/lcov.info
retention-days: 1
coverage:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
- name: Install lcov
shell: bash
run: sudo apt-get -y install lcov
- name: Install lcov
shell: bash
run: sudo apt-get -y install lcov
- name: Collect coverage reports
uses: actions/download-artifact@v3
with:
name: coverage
path: ./coverage
- name: Collect coverage reports
uses: actions/download-artifact@v4
with:
path: ./coverage
pattern: coverage-node-*
- name: Merge coverage reports
shell: bash
run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./coverage/lcov.info
- name: Merge coverage reports
shell: bash
run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info
- name: Upload coverage report
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload coverage report
uses: coverallsapp/github-action@v2
with:
file: ./lcov.info

View File

@@ -1,3 +1,32 @@
4.22.0 / 2025-12-01
==========
* Security fix for [CVE-2024-51999](https://www.cve.org/CVERecord?id=CVE-2024-51999) ([GHSA-pj86-cfqh-vqx6](https://github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6))
* deps: use tilde notation for dependencies
* deps: qs@6.14.0
4.21.2 / 2024-11-06
==========
* deps: path-to-regexp@0.1.12
- Fix backtracking protection
* deps: path-to-regexp@0.1.11
- Throws an error on invalid path values
4.21.1 / 2024-10-08
==========
* Backported a fix for [CVE-2024-47764](https://nvd.nist.gov/vuln/detail/CVE-2024-47764)
4.21.0 / 2024-09-11
==========
* Deprecate `res.location("back")` and `res.redirect("back")` magic string
* deps: serve-static@1.16.2
* includes send@0.19.0
* deps: finalhandler@1.3.1
* deps: qs@6.13.0
4.20.0 / 2024-09-10
==========
* deps: serve-static@0.16.0

View File

@@ -32,7 +32,8 @@ app.param(['to', 'from'], function(req, res, next, num, name){
// Load user by id
app.param('user', function(req, res, next, id){
if (req.user = users[id]) {
req.user = users[id];
if (req.user) {
next();
} else {
next(createError(404, 'failed to find user'));

View File

@@ -916,6 +916,7 @@ res.location = function location(url) {
// "back" is an alias for the referrer
if (url === 'back') {
deprecate('res.location("back"): use res.location(req.get("Referrer") || "/") and refer to https://dub.sh/security-redirect for best practices');
loc = this.req.get('Referrer') || '/';
} else {
loc = String(url);

View File

@@ -287,7 +287,7 @@ function createETagGenerator (options) {
function parseExtendedQueryString(str) {
return qs.parse(str, {
allowPrototypes: true
plainObjects: true
});
}

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Fast, unopinionated, minimalist web framework",
"version": "4.20.0",
"version": "4.22.0",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
@@ -15,6 +15,10 @@
"license": "MIT",
"repository": "expressjs/express",
"homepage": "http://expressjs.com/",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
},
"keywords": [
"express",
"framework",
@@ -30,32 +34,32 @@
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.10",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.19.0",
"serve-static": "1.16.0",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
@@ -71,11 +75,11 @@
"hbs": "4.2.0",
"marked": "0.7.0",
"method-override": "3.0.0",
"mocha": "10.2.0",
"mocha": "^6.2.2",
"morgan": "1.10.0",
"nyc": "15.1.0",
"nyc": "^14.1.1",
"pbkdf2-password": "1.2.1",
"supertest": "6.3.0",
"supertest": "^6.1.6",
"vhost": "~3.0.2"
},
"engines": {

View File

@@ -55,6 +55,17 @@ describe('app.router', function(){
.expect(200, done)
})
it('should not support ' + method.toUpperCase() + ' without a path', function () {
if (method === 'get' || (method === 'query' && shouldSkipQuery(process.versions.node))) {
this.skip();
}
var app = express();
assert.throws(function () {
app[method](function (req, res) { });
});
});
it('should reject numbers for app.' + method, function(){
var app = express();
assert.throws(app[method].bind(app, '/', 3), /Number/)

View File

@@ -3,6 +3,7 @@
var assert = require('assert')
var express = require('../')
, request = require('supertest');
var qs = require('qs');
describe('req', function(){
describe('.query', function(){
@@ -38,6 +39,22 @@ describe('req', function(){
.get('/?user.name=tj')
.expect(200, '{"user.name":"tj"}', done);
});
it('should not be able to access object prototype properties', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?foo=yee')
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
});
it('should be able to use object prototype property names as keys', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?hasOwnProperty=yee')
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
});
});
describe('when "query parser" is simple', function () {
@@ -48,6 +65,22 @@ describe('req', function(){
.get('/?user%5Bname%5D=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
it('should not be able to access object prototype properties', function (done) {
var app = createApp('simple', true);
request(app)
.get('/?foo=yee')
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
});
it('should be able to use object prototype property names as keys', function (done) {
var app = createApp('simple', true);
request(app)
.get('/?hasOwnProperty=yee')
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
});
});
describe('when "query parser" is a function', function () {
@@ -60,6 +93,18 @@ describe('req', function(){
.get('/?user%5Bname%5D=tj')
.expect(200, '{"length":17}', done);
});
// test exists to verify behavior for folks wishing to workaround our qs defaults
it('should drop object prototype property names and be able to access object prototype properties', function (done) {
var app = createApp(
function (str) {
return qs.parse(str)
}, true);
request(app)
.get('/?hasOwnProperty=biscuits')
.expect(200, '{"query":{},"hasOwnProperty":false}', done);
});
});
describe('when "query parser" disabled', function () {
@@ -70,6 +115,22 @@ describe('req', function(){
.get('/?user%5Bname%5D=tj')
.expect(200, '{}', done);
});
it('should not be able to access object prototype properties', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?foo=yee')
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
});
it('should be able to use object prototype property names as keys', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?hasOwnProperty=yee')
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
});
});
describe('when "query parser" enabled', function () {
@@ -80,6 +141,22 @@ describe('req', function(){
.get('/?user%5Bname%5D=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
it('should not be able to access object prototype properties', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?foo=yee')
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
});
it('should be able to use object prototype property names as keys', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?hasOwnProperty=yee')
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
});
});
describe('when "query parser fn" is missing', function () {
@@ -97,6 +174,22 @@ describe('req', function(){
.get('/?user[name]=tj&user.name=tj')
.expect(200, '{"user":{"name":"tj"},"user.name":"tj"}', done);
});
it('should not be able to access object prototype properties', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?foo=yee')
.expect(200, /TypeError: req\.query\.hasOwnProperty is not a function/, done);
});
it('should be able to use object prototype property names as keys', function (done) {
var app = createApp('extended', true);
request(app)
.get('/?hasOwnProperty=yee')
.expect(200, '{"query":{"hasOwnProperty":"yee"},"error":"TypeError: req.query.hasOwnProperty is not a function"}', done);
});
});
describe('when "query parser" an unknown value', function () {
@@ -108,7 +201,7 @@ describe('req', function(){
})
})
function createApp(setting) {
function createApp(setting, isPrototypePropertyTest) {
var app = express();
if (setting !== undefined) {
@@ -116,7 +209,17 @@ function createApp(setting) {
}
app.use(function (req, res) {
res.send(req.query);
if(isPrototypePropertyTest) {
try {
var hasOwnProperty = req.query.hasOwnProperty('✨ express ✨');
res.send({ query: req.query, hasOwnProperty: hasOwnProperty });
} catch (error) {
res.send({ query: req.query, error: error.toString() });
}
}
else {
res.send(req.query);
}
});
return app;

View File

@@ -77,10 +77,10 @@ function getMajorVersion(versionString) {
}
function shouldSkipQuery(versionString) {
// Skipping HTTP QUERY tests on Node 21, it is reported in http.METHODS on 21.7.2 but not supported
// update this implementation to run on supported versions of 21 once they exist
// Skipping HTTP QUERY tests below Node 22, QUERY wasn't fully supported by Node until 22
// we could update this implementation to run on supported versions of 21 once they exist
// upstream tracking https://github.com/nodejs/node/issues/51562
// express tracking issue: https://github.com/expressjs/express/issues/5615
return Number(getMajorVersion(versionString)) === 21
return Number(getMajorVersion(versionString)) < 22
}