diff --git a/CHANGELOG.md b/CHANGELOG.md index 778e06f28db1..6d3b8eb514d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ - Fixed configurability and `ToString` conversion of some accessors - Added Opera Android 73 compat data mapping - Added TypeScript definitions to `core-js-builder` +- Added proper error on the excess number of trailing `=` in the `atob` polyfill +- Fixed theoretically possible ReDoS vulnerabilities in `String.prototype.{ trim, trimEnd, trimRight }`, `atob`, and `URL` polyfills in some ancient engines ##### [3.27.2 - 2023.01.19](https://github.com/zloirock/core-js/releases/tag/v3.27.2) - [`Set` methods proposal](https://github.com/tc39/proposal-set-methods) updates: diff --git a/packages/core-js/internals/engine-is-ios.js b/packages/core-js/internals/engine-is-ios.js index 1c17c0b7c50c..ce88f24c3954 100644 --- a/packages/core-js/internals/engine-is-ios.js +++ b/packages/core-js/internals/engine-is-ios.js @@ -1,3 +1,4 @@ var userAgent = require('../internals/engine-user-agent'); +// eslint-disable-next-line redos/no-vulnerable -- safe module.exports = /(?:ipad|iphone|ipod).*applewebkit/i.test(userAgent); diff --git a/packages/core-js/internals/error-stack-clear.js b/packages/core-js/internals/error-stack-clear.js index 5f193e9a8979..ba70532af2ac 100644 --- a/packages/core-js/internals/error-stack-clear.js +++ b/packages/core-js/internals/error-stack-clear.js @@ -4,6 +4,7 @@ var $Error = Error; var replace = uncurryThis(''.replace); var TEST = (function (arg) { return String($Error(arg).stack); })('zxcasd'); +// eslint-disable-next-line redos/no-vulnerable -- safe var V8_OR_CHAKRA_STACK_ENTRY = /\n\s*at [^:]*:[^\n]*/; var IS_V8_OR_CHAKRA_STACK = V8_OR_CHAKRA_STACK_ENTRY.test(TEST); diff --git a/packages/core-js/internals/get-substitution.js b/packages/core-js/internals/get-substitution.js index 60ad4dc1d418..ef91e12aa813 100644 --- a/packages/core-js/internals/get-substitution.js +++ b/packages/core-js/internals/get-substitution.js @@ -5,6 +5,7 @@ var floor = Math.floor; var charAt = uncurryThis(''.charAt); var replace = uncurryThis(''.replace); var stringSlice = uncurryThis(''.slice); +// eslint-disable-next-line redos/no-vulnerable -- safe var SUBSTITUTION_SYMBOLS = /\$([$&'`]|\d{1,2}|<[^>]*>)/g; var SUBSTITUTION_SYMBOLS_NO_NAMED = /\$([$&'`]|\d{1,2})/g; diff --git a/packages/core-js/internals/string-trim.js b/packages/core-js/internals/string-trim.js index 7fc16558dfab..33ac6e04ee50 100644 --- a/packages/core-js/internals/string-trim.js +++ b/packages/core-js/internals/string-trim.js @@ -4,16 +4,15 @@ var toString = require('../internals/to-string'); var whitespaces = require('../internals/whitespaces'); var replace = uncurryThis(''.replace); -var whitespace = '[' + whitespaces + ']'; -var ltrim = RegExp('^' + whitespace + whitespace + '*'); -var rtrim = RegExp(whitespace + whitespace + '*$'); +var ltrim = RegExp('^[' + whitespaces + ']+'); +var rtrim = RegExp('(^|[^' + whitespaces + '])[' + whitespaces + ']+$'); // `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation var createMethod = function (TYPE) { return function ($this) { var string = toString(requireObjectCoercible($this)); if (TYPE & 1) string = replace(string, ltrim, ''); - if (TYPE & 2) string = replace(string, rtrim, ''); + if (TYPE & 2) string = replace(string, rtrim, '$1'); return string; }; }; diff --git a/packages/core-js/internals/typed-array-constructor.js b/packages/core-js/internals/typed-array-constructor.js index 105bb2a4f140..4ed4e3e22400 100644 --- a/packages/core-js/internals/typed-array-constructor.js +++ b/packages/core-js/internals/typed-array-constructor.js @@ -121,7 +121,7 @@ if (DESCRIPTORS) { }); module.exports = function (TYPE, wrapper, CLAMPED) { - var BYTES = TYPE.match(/\d+$/)[0] / 8; + var BYTES = TYPE.match(/\d+/)[0] / 8; var CONSTRUCTOR_NAME = TYPE + (CLAMPED ? 'Clamped' : '') + 'Array'; var GETTER = 'get' + TYPE; var SETTER = 'set' + TYPE; diff --git a/packages/core-js/modules/web.atob.js b/packages/core-js/modules/web.atob.js index d5efd344d274..f0f7a3ae7db4 100644 --- a/packages/core-js/modules/web.atob.js +++ b/packages/core-js/modules/web.atob.js @@ -11,7 +11,7 @@ var ctoi = require('../internals/base64-map').ctoi; var disallowed = /[^\d+/a-z]/i; var whitespaces = /[\t\n\f\r ]+/g; -var finalEq = /[=]+$/; +var finalEq = /[=]{1,2}$/; var $atob = getBuiltIn('atob'); var fromCharCode = String.fromCharCode; diff --git a/packages/core-js/modules/web.url.constructor.js b/packages/core-js/modules/web.url.constructor.js index b4eb9f60933c..5cf2f5e2c6de 100644 --- a/packages/core-js/modules/web.url.constructor.js +++ b/packages/core-js/modules/web.url.constructor.js @@ -61,7 +61,8 @@ var HEX = /^[\da-f]+$/i; /* eslint-disable regexp/no-control-character -- safe */ var FORBIDDEN_HOST_CODE_POINT = /[\0\t\n\r #%/:<>?@[\\\]^|]/; var FORBIDDEN_HOST_CODE_POINT_EXCLUDING_PERCENT = /[\0\t\n\r #/:<>?@[\\\]^|]/; -var LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g; +var LEADING_C0_CONTROL_OR_SPACE = /^[\u0000-\u0020]+/; +var TRAILING_C0_CONTROL_OR_SPACE = /(^|[^\u0000-\u0020])[\u0000-\u0020]+$/; var TAB_AND_NEW_LINE = /[\t\n\r]/g; /* eslint-enable regexp/no-control-character -- safe */ var EOF; @@ -357,7 +358,8 @@ URLState.prototype = { url.query = null; url.fragment = null; url.cannotBeABaseURL = false; - input = replace(input, LEADING_AND_TRAILING_C0_CONTROL_OR_SPACE, ''); + input = replace(input, LEADING_C0_CONTROL_OR_SPACE, ''); + input = replace(input, TRAILING_C0_CONTROL_OR_SPACE, '$1'); } input = replace(input, TAB_AND_NEW_LINE, ''); diff --git a/tests/eslint/eslint.config.js b/tests/eslint/eslint.config.js index 45fcd3a9c083..779096ac18aa 100644 --- a/tests/eslint/eslint.config.js +++ b/tests/eslint/eslint.config.js @@ -10,6 +10,7 @@ const pluginJSONC = require('eslint-plugin-jsonc'); const pluginN = require('eslint-plugin-n'); const pluginPromise = require('eslint-plugin-promise'); const pluginQUnit = require('eslint-plugin-qunit'); +const pluginReDoS = require('eslint-plugin-redos'); const pluginRegExp = require('eslint-plugin-regexp'); const pluginSonarJS = require('eslint-plugin-sonarjs'); const pluginUnicorn = require('eslint-plugin-unicorn'); @@ -625,8 +626,6 @@ const base = { 'regexp/no-potentially-useless-backreference': ERROR, // disallow standalone backslashes 'regexp/no-standalone-backslash': ERROR, - // disallow exponential and polynomial backtracking - 'regexp/no-super-linear-backtracking': ERROR, // disallow trivially nested assertions 'regexp/no-trivially-nested-assertion': ERROR, // disallow nested quantifiers that can be rewritten as one quantifier @@ -701,6 +700,8 @@ const base = { 'regexp/unicode-escape': ERROR, // use the `i` flag if it simplifies the pattern 'regexp/use-ignore-case': ERROR, + // ReDoS vulnerability check + 'redos/no-vulnerable': [ERROR, { timeout: 1e3 }], // disallow function declarations in if statement clauses without using blocks 'es/no-function-declarations-in-if-statement-clauses-without-block': ERROR, @@ -1043,6 +1044,8 @@ const nodeDev = { ...forbidES2023BuiltIns, 'es/no-intl-supportedvaluesof': ERROR, ...forbidES2023IntlBuiltIns, + // ReDoS vulnerability check + 'redos/no-vulnerable': OFF, }; const tests = { @@ -1071,6 +1074,8 @@ const tests = { 'unicorn/error-message': OFF, // functions should not have identical implementations 'sonarjs/no-identical-functions': OFF, + // ReDoS vulnerability check + 'redos/no-vulnerable': OFF, // allow Annex B methods for testing ...disable(forbidESAnnexBBuiltIns), }; @@ -1270,6 +1275,7 @@ module.exports = [ node: pluginN, promise: pluginPromise, qunit: pluginQUnit, + redos: pluginReDoS, regexp: pluginRegExp, sonarjs: pluginSonarJS, unicorn: pluginUnicorn, diff --git a/tests/eslint/package-lock.json b/tests/eslint/package-lock.json index 70c005564878..dda5ba16ea97 100644 --- a/tests/eslint/package-lock.json +++ b/tests/eslint/package-lock.json @@ -16,6 +16,7 @@ "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-qunit": "^7.3.4", + "eslint-plugin-redos": "^4.4.3", "eslint-plugin-regexp": "^1.12.0", "eslint-plugin-sonarjs": "~0.18.0", "eslint-plugin-unicorn": "^45.0.2", @@ -1067,6 +1068,21 @@ "node": "12.x || 14.x || >=16.0.0" } }, + "node_modules/eslint-plugin-redos": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-redos/-/eslint-plugin-redos-4.4.3.tgz", + "integrity": "sha512-MfjXUMZv31T1i5mRG2InyFh1oCewM9cTPu3FSIVtL1gT5SN5eOpF9KssLplqtaRhm5mhoXJRhKnF3/q/2bXhKQ==", + "dev": true, + "dependencies": { + "recheck": "4.4.3" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "eslint": ">= 3" + } + }, "node_modules/eslint-plugin-regexp": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-1.12.0.tgz", @@ -2469,6 +2485,67 @@ "node": ">=8" } }, + "node_modules/recheck": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/recheck/-/recheck-4.4.3.tgz", + "integrity": "sha512-jHr9KyOI+jdsoFaJZz0eQjQmNFfIGafOnQPxs53nxPVBMKuqjX6W5NI4yPjQSwccBkgEeH4n4UMaM4OgAjJXog==", + "dev": true, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "recheck-jar": "4.4.3", + "recheck-linux-x64": "4.4.3", + "recheck-macos-x64": "4.4.3", + "recheck-windows-x64": "4.4.3" + } + }, + "node_modules/recheck-jar": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/recheck-jar/-/recheck-jar-4.4.3.tgz", + "integrity": "sha512-SyJZvZglIH3XjF7YQUIbsxNpQpcqyNj/JwgjxOTsLvD/FLEux6hl0/13rOBuvRGZuWPX+MT+7QW8MlP2MAiNTw==", + "dev": true, + "optional": true + }, + "node_modules/recheck-linux-x64": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/recheck-linux-x64/-/recheck-linux-x64-4.4.3.tgz", + "integrity": "sha512-fO7YH2bAjN3T0mgqNjtcVWxpFyHhKWN8fhD8ET6itGsnUs1panfKJ/3DX/rH5E3EFEq3G+rahqrAnvA9M9a2gg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/recheck-macos-x64": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/recheck-macos-x64/-/recheck-macos-x64-4.4.3.tgz", + "integrity": "sha512-bxvtk4jt0jY6SSvUCQxYoYk3WqJwfMKk4wo6XgVk/qLqosxNWh9rU8dBBgBZXVzAkLVTVyjYJPV+DqmNZyg4ig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/recheck-windows-x64": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/recheck-windows-x64/-/recheck-windows-x64-4.4.3.tgz", + "integrity": "sha512-vmRXiLV9Ui+I4Rn0DOW0sFLpyLejDYGuegxmnrGRfhR3bontBW7Ft2QDP5cvZESckGlgY/5NOjqTUJ6siwq1lw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/refa": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/refa/-/refa-0.9.1.tgz", diff --git a/tests/eslint/package.json b/tests/eslint/package.json index 9dbb88e5f4d7..a0d9dd731e4e 100644 --- a/tests/eslint/package.json +++ b/tests/eslint/package.json @@ -11,6 +11,7 @@ "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-qunit": "^7.3.4", + "eslint-plugin-redos": "^4.4.3", "eslint-plugin-regexp": "^1.12.0", "eslint-plugin-sonarjs": "~0.18.0", "eslint-plugin-unicorn": "^45.0.2", diff --git a/tests/unit-global/es.string.replace.js b/tests/unit-global/es.string.replace.js index b75723703f3f..fa3c7d3e4aad 100644 --- a/tests/unit-global/es.string.replace.js +++ b/tests/unit-global/es.string.replace.js @@ -1,4 +1,4 @@ -/* eslint-disable regexp/no-super-linear-backtracking, regexp/no-unused-capturing-group -- required for testing */ +/* eslint-disable regexp/no-unused-capturing-group -- required for testing */ import { GLOBAL, NATIVE, STRICT } from '../helpers/constants'; import { patchRegExp$exec } from '../helpers/helpers';