Critical severityNVD Advisory· Published Feb 12, 2025· Updated Feb 12, 2025
Koa has Inefficient Regular Expression Complexity
CVE-2025-25200
Description
Koa is expressive middleware for Node.js using ES2017 async functions. Prior to versions 0.21.2, 1.7.1, 2.15.4, and 3.0.0-alpha.3, Koa uses an evil regex to parse the X-Forwarded-Proto and X-Forwarded-Host HTTP headers. This can be exploited to carry out a Denial-of-Service attack. Versions 0.21.2, 1.7.1, 2.15.4, and 3.0.0-alpha.3 fix the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
koanpm | >= 2.0.0, < 2.15.4 | 2.15.4 |
koanpm | >= 3.0.0-alpha.0, < 3.0.0-alpha.3 | 3.0.0-alpha.3 |
koanpm | >= 1.0.0, < 1.7.1 | 1.7.1 |
koanpm | < 0.21.2 | 0.21.2 |
Affected products
1Patches
33 files changed · +21 −4
History.md+5 −0 modified@@ -1,4 +1,9 @@ +2.15.4 / 2025-02-11 +================== + +fix: avoid redos on host and protocol getter + 2.15.3 / 2024-04-11 ==================
lib/request.js+15 −3 modified@@ -257,7 +257,7 @@ module.exports = { if (!host) host = this.get('Host'); } if (!host) return ''; - return host.split(/\s*,\s*/, 1)[0]; + return splitCommaSeparatedValues(host, 1)[0]; }, /** @@ -402,7 +402,7 @@ module.exports = { if (this.socket.encrypted) return 'https'; if (!this.app.proxy) return 'http'; const proto = this.get('X-Forwarded-Proto'); - return proto ? proto.split(/\s*,\s*/, 1)[0] : 'http'; + return proto ? splitCommaSeparatedValues(proto, 1)[0] : 'http'; }, /** @@ -434,7 +434,7 @@ module.exports = { const proxy = this.app.proxy; const val = this.get(this.app.proxyIpHeader); let ips = proxy && val - ? val.split(/\s*,\s*/) + ? splitCommaSeparatedValues(val) : []; if (this.app.maxIpsCount > 0) { ips = ips.slice(-this.app.maxIpsCount); @@ -724,3 +724,15 @@ module.exports = { if (util.inspect.custom) { module.exports[util.inspect.custom] = module.exports.inspect; } + +/** + * Split a comma-separated value string into an array of values, with an optional limit. + * All the values are trimmed of whitespace. + * + * @param {string} value - The comma-separated value string to split. + * @param {number} [limit] - The maximum number of values to return. + * @returns {string[]} An array of values from the comma-separated string. + */ +function splitCommaSeparatedValues(value, limit) { + return value.split(',', limit).map(v => v.trim()); +}
package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "koa", - "version": "2.15.3", + "version": "2.15.4", "description": "Koa web app framework", "main": "lib/application.js", "exports": {
4 files changed · +33 −6
History.md+5 −0 modified@@ -1,4 +1,9 @@ +1.7.1 / 2025-02-11 +================== + +* fix: avoid redos on host and protocol getter + 1.7.0 / 2019-10-17 ==================
lib/request.js+15 −3 modified@@ -230,7 +230,7 @@ module.exports = { var host = proxy && this.get('X-Forwarded-Host'); host = host || this.get('Host'); if (!host) return ''; - return host.split(/\s*,\s*/)[0]; + return splitCommaSeparatedValues(host, 1)[0]; }, /** @@ -358,7 +358,7 @@ module.exports = { if (this.socket.encrypted) return 'https'; if (!proxy) return 'http'; var proto = this.get('X-Forwarded-Proto') || 'http'; - return proto.split(/\s*,\s*/)[0]; + return splitCommaSeparatedValues(proto, 1)[0]; }, /** @@ -403,7 +403,7 @@ module.exports = { var proxy = this.app.proxy; var val = this.get('X-Forwarded-For'); return proxy && val - ? val.split(/\s*,\s*/) + ? splitCommaSeparatedValues(val) : []; }, @@ -634,3 +634,15 @@ module.exports = { }; } }; + +/** + * Split a comma-separated value string into an array of values, with an optional limit. + * All the values are trimmed of whitespace. + * + * @param {string} value - The comma-separated value string to split. + * @param {number} [limit] - The maximum number of values to return. + * @returns {string[]} An array of values from the comma-separated string. + */ +function splitCommaSeparatedValues(value, limit) { + return value.split(',', limit).map(v => v.trim()); +}
package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "koa", - "version": "1.7.0", + "version": "1.7.1", "description": "Koa web app framework", "main": "lib/application.js", "scripts": {
test/application.js+12 −2 modified@@ -79,7 +79,15 @@ describe('app.inspect()', function(){ var app = koa(); var util = require('util'); var str = util.inspect(app); - assert.equal("{ subdomainOffset: 2, proxy: false, env: 'test' }", str); + assert.equal('Application {\n' + + " env: 'test',\n" + + ' subdomainOffset: 2,\n' + + ' middleware: [],\n' + + ' proxy: false,\n' + + ' context: {},\n' + + ' request: {},\n' + + ' response: {}\n' + + '}', str); }) }) @@ -215,7 +223,9 @@ describe('app.onerror(err)', function(){ describe('app.respond', function(){ describe('when this.respond === false', function(){ - it('should bypass app.respond', function(done){ + // no more work on `supertest`, skip it + // TypeError: Cannot read properties of null (reading 'text') + it.skip('should bypass app.respond', function(done){ var app = koa(); app.use(function *(){
3 files changed · +22 −4
History.md+6 −0 modified@@ -1,4 +1,10 @@ +3.0.0-alpha.3 / 2025-02-11 +================== + +**fixes** +- Avoid redos on host and protocol getter + 3.0.0-alpha.2 / 2024-11-04 ==================
lib/request.js+15 −3 modified@@ -256,7 +256,7 @@ module.exports = { if (!host) host = this.get('Host') } if (!host) return '' - return host.split(/\s*,\s*/, 1)[0] + return splitCommaSeparatedValues(host, 1)[0] }, /** @@ -401,7 +401,7 @@ module.exports = { if (this.socket.encrypted) return 'https' if (!this.app.proxy) return 'http' const proto = this.get('X-Forwarded-Proto') - return proto ? proto.split(/\s*,\s*/, 1)[0] : 'http' + return proto ? splitCommaSeparatedValues(proto, 1)[0] : 'http' }, /** @@ -433,7 +433,7 @@ module.exports = { const proxy = this.app.proxy const val = this.get(this.app.proxyIpHeader) let ips = proxy && val - ? val.split(/\s*,\s*/) + ? splitCommaSeparatedValues(val) : [] if (this.app.maxIpsCount > 0) { ips = ips.slice(-this.app.maxIpsCount) @@ -723,3 +723,15 @@ module.exports = { if (util.inspect.custom) { module.exports[util.inspect.custom] = module.exports.inspect } + +/** + * Split a comma-separated value string into an array of values, with an optional limit. + * All the values are trimmed of whitespace. + * + * @param {string} value - The comma-separated value string to split. + * @param {number} [limit] - The maximum number of values to return. + * @returns {string[]} An array of values from the comma-separated string. + */ +function splitCommaSeparatedValues(value, limit) { + return value.split(',', limit).map(v => v.trim()); +}
package.json+1 −1 modified@@ -1,6 +1,6 @@ { "name": "koa", - "version": "3.0.0-alpha.2", + "version": "3.0.0-alpha.3", "publishConfig": { "tag": "experimental" },
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-593f-38f6-jp5mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-25200ghsaADVISORY
- github.com/koajs/koa/blob/master/lib/request.jsmitrex_refsource_MISC
- github.com/koajs/koa/blob/master/lib/request.jsmitrex_refsource_MISC
- github.com/koajs/koa/commit/5054af6e31ffd451a4151a1fe144cef6e5d0d83cghsax_refsource_MISCWEB
- github.com/koajs/koa/commit/5f294bb1c7c8d9c61904378d250439a321bffd32ghsax_refsource_MISCWEB
- github.com/koajs/koa/commit/93fe903fc966635a991bcf890cfc3427d33a1a08ghsax_refsource_MISCWEB
- github.com/koajs/koa/releases/tag/2.15.4ghsax_refsource_MISCWEB
- github.com/koajs/koa/security/advisories/GHSA-593f-38f6-jp5mghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.