terser 5.14.1 → 5.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.14.2
4
+
5
+ - Security fix for RegExps that should not be evaluated (regexp DDOS)
6
+ - Source maps improvements (#1211)
7
+ - Performance improvements in long property access evaluation (#1213)
8
+
3
9
  ## v5.14.1
4
10
  - keep_numbers option added to TypeScript defs (#1208)
5
11
  - Fixed parsing of nested template strings (#1204)
@@ -206,6 +212,13 @@ Hotfix release, fixes package.json "engines" syntax
206
212
  - Module is now distributed as a dual package - You can `import` and `require()` too.
207
213
  - Inline improvements were made
208
214
 
215
+
216
+ -----
217
+
218
+ ## v4.8.1 (backport)
219
+
220
+ - Security fix for RegExps that should not be evaluated (regexp DDOS)
221
+
209
222
  ## v4.8.0
210
223
 
211
224
  - Support for numeric separators (`million = 1_000_000`) was added.
@@ -253,7 +253,15 @@ function regexp_source_fix(source) {
253
253
  return (escaped ? "" : "\\") + lineTerminatorEscape[match];
254
254
  });
255
255
  }
256
- const all_flags = "gimuy";
256
+
257
+ // Subset of regexps that is not going to cause regexp based DDOS
258
+ // https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
259
+ const re_safe_regexp = /^[\\/|\0\s\w^$.[\]()]*$/;
260
+
261
+ /** Check if the regexp is safe for Terser to create without risking a RegExp DOS */
262
+ const regexp_is_safe = (source) => re_safe_regexp.test(source);
263
+
264
+ const all_flags = "dgimsuy";
257
265
  function sort_regexp_flags(flags) {
258
266
  const existing_flags = new Set(flags.split(""));
259
267
  let out = "";
@@ -9640,12 +9648,14 @@ function OutputStream(options) {
9640
9648
  output.with_indent(output.next_indent(), function() {
9641
9649
  output.append_comments(self, true);
9642
9650
  });
9651
+ output.add_mapping(self.end);
9643
9652
  output.print("}");
9644
9653
  }
9645
9654
  function print_braced(self, output, allow_directives) {
9646
9655
  if (self.body.length > 0) {
9647
9656
  output.with_block(function() {
9648
9657
  display_body(self.body, false, output, allow_directives);
9658
+ output.add_mapping(self.end);
9649
9659
  });
9650
9660
  } else print_braced_empty(self, output);
9651
9661
  }
@@ -13851,7 +13861,7 @@ def_eval(AST_BigInt, return_this);
13851
13861
 
13852
13862
  def_eval(AST_RegExp, function (compressor) {
13853
13863
  let evaluated = compressor.evaluated_regexps.get(this.value);
13854
- if (evaluated === undefined) {
13864
+ if (evaluated === undefined && regexp_is_safe(this.value.source)) {
13855
13865
  try {
13856
13866
  const { source, flags } = this.value;
13857
13867
  evaluated = new RegExp(source, flags);
@@ -14064,7 +14074,7 @@ const regexp_flags = new Set([
14064
14074
  ]);
14065
14075
 
14066
14076
  def_eval(AST_PropAccess, function (compressor, depth) {
14067
- const obj = this.expression._eval(compressor, depth);
14077
+ let obj = this.expression._eval(compressor, depth + 1);
14068
14078
  if (obj === nullish || (this.optional && obj == null)) return nullish;
14069
14079
  if (compressor.option("unsafe")) {
14070
14080
  var key = this.property;
@@ -14074,7 +14084,6 @@ def_eval(AST_PropAccess, function (compressor, depth) {
14074
14084
  return this;
14075
14085
  }
14076
14086
  var exp = this.expression;
14077
- var val;
14078
14087
  if (is_undeclared_ref(exp)) {
14079
14088
 
14080
14089
  var aa;
@@ -14091,29 +14100,29 @@ def_eval(AST_PropAccess, function (compressor, depth) {
14091
14100
  }
14092
14101
  if (!is_pure_native_value(exp.name, key))
14093
14102
  return this;
14094
- val = global_objs[exp.name];
14103
+ obj = global_objs[exp.name];
14095
14104
  } else {
14096
- val = exp._eval(compressor, depth + 1);
14097
- if (val instanceof RegExp) {
14105
+ if (obj instanceof RegExp) {
14098
14106
  if (key == "source") {
14099
- return regexp_source_fix(val.source);
14107
+ return regexp_source_fix(obj.source);
14100
14108
  } else if (key == "flags" || regexp_flags.has(key)) {
14101
- return val[key];
14109
+ return obj[key];
14102
14110
  }
14103
14111
  }
14104
- if (!val || val === exp || !HOP(val, key))
14112
+ if (!obj || obj === exp || !HOP(obj, key))
14105
14113
  return this;
14106
- if (typeof val == "function")
14114
+
14115
+ if (typeof obj == "function")
14107
14116
  switch (key) {
14108
14117
  case "name":
14109
- return val.node.name ? val.node.name.name : "";
14118
+ return obj.node.name ? obj.node.name.name : "";
14110
14119
  case "length":
14111
- return val.node.length_property();
14120
+ return obj.node.length_property();
14112
14121
  default:
14113
14122
  return this;
14114
14123
  }
14115
14124
  }
14116
- return val[key];
14125
+ return obj[key];
14117
14126
  }
14118
14127
  return this;
14119
14128
  });
@@ -18992,6 +19001,7 @@ def_optimize(AST_Call, function(self, compressor) {
18992
19001
  params.push(value);
18993
19002
  return arg !== value;
18994
19003
  })
19004
+ && regexp_is_safe(params[0])
18995
19005
  ) {
18996
19006
  let [ source, flags ] = params;
18997
19007
  source = regexp_source_fix(new RegExp(source).source);
@@ -20651,6 +20661,12 @@ def_optimize(AST_Dot, function(self, compressor) {
20651
20661
  const sub = self.flatten_object(self.property, compressor);
20652
20662
  if (sub) return sub.optimize(compressor);
20653
20663
  }
20664
+
20665
+ if (self.expression instanceof AST_PropAccess
20666
+ && parent instanceof AST_PropAccess) {
20667
+ return self;
20668
+ }
20669
+
20654
20670
  let ev = self.evaluate(compressor);
20655
20671
  if (ev !== self) {
20656
20672
  ev = make_node_from_constant(ev, self).optimize(compressor);
@@ -46,7 +46,8 @@ import {
46
46
  makePredicate,
47
47
  return_this,
48
48
  string_template,
49
- regexp_source_fix
49
+ regexp_source_fix,
50
+ regexp_is_safe,
50
51
  } from "../utils/index.js";
51
52
  import {
52
53
  AST_Array,
@@ -129,7 +130,7 @@ def_eval(AST_BigInt, return_this);
129
130
 
130
131
  def_eval(AST_RegExp, function (compressor) {
131
132
  let evaluated = compressor.evaluated_regexps.get(this.value);
132
- if (evaluated === undefined) {
133
+ if (evaluated === undefined && regexp_is_safe(this.value.source)) {
133
134
  try {
134
135
  const { source, flags } = this.value;
135
136
  evaluated = new RegExp(source, flags);
@@ -342,7 +343,7 @@ const regexp_flags = new Set([
342
343
  ]);
343
344
 
344
345
  def_eval(AST_PropAccess, function (compressor, depth) {
345
- const obj = this.expression._eval(compressor, depth);
346
+ let obj = this.expression._eval(compressor, depth + 1);
346
347
  if (obj === nullish || (this.optional && obj == null)) return nullish;
347
348
  if (compressor.option("unsafe")) {
348
349
  var key = this.property;
@@ -352,7 +353,6 @@ def_eval(AST_PropAccess, function (compressor, depth) {
352
353
  return this;
353
354
  }
354
355
  var exp = this.expression;
355
- var val;
356
356
  if (is_undeclared_ref(exp)) {
357
357
 
358
358
  var aa;
@@ -369,29 +369,29 @@ def_eval(AST_PropAccess, function (compressor, depth) {
369
369
  }
370
370
  if (!is_pure_native_value(exp.name, key))
371
371
  return this;
372
- val = global_objs[exp.name];
372
+ obj = global_objs[exp.name];
373
373
  } else {
374
- val = exp._eval(compressor, depth + 1);
375
- if (val instanceof RegExp) {
374
+ if (obj instanceof RegExp) {
376
375
  if (key == "source") {
377
- return regexp_source_fix(val.source);
376
+ return regexp_source_fix(obj.source);
378
377
  } else if (key == "flags" || regexp_flags.has(key)) {
379
- return val[key];
378
+ return obj[key];
380
379
  }
381
380
  }
382
- if (!val || val === exp || !HOP(val, key))
381
+ if (!obj || obj === exp || !HOP(obj, key))
383
382
  return this;
384
- if (typeof val == "function")
383
+
384
+ if (typeof obj == "function")
385
385
  switch (key) {
386
386
  case "name":
387
- return val.node.name ? val.node.name.name : "";
387
+ return obj.node.name ? obj.node.name.name : "";
388
388
  case "length":
389
- return val.node.length_property();
389
+ return obj.node.length_property();
390
390
  default:
391
391
  return this;
392
392
  }
393
393
  }
394
- return val[key];
394
+ return obj[key];
395
395
  }
396
396
  return this;
397
397
  });
@@ -158,6 +158,7 @@ import {
158
158
  return_true,
159
159
  regexp_source_fix,
160
160
  has_annotation,
161
+ regexp_is_safe,
161
162
  } from "../utils/index.js";
162
163
  import { first_in_statement } from "../utils/first_in_statement.js";
163
164
  import { equivalent_to } from "../equivalent-to.js";
@@ -2140,6 +2141,7 @@ def_optimize(AST_Call, function(self, compressor) {
2140
2141
  params.push(value);
2141
2142
  return arg !== value;
2142
2143
  })
2144
+ && regexp_is_safe(params[0])
2143
2145
  ) {
2144
2146
  let [ source, flags ] = params;
2145
2147
  source = regexp_source_fix(new RegExp(source).source);
@@ -3799,6 +3801,12 @@ def_optimize(AST_Dot, function(self, compressor) {
3799
3801
  const sub = self.flatten_object(self.property, compressor);
3800
3802
  if (sub) return sub.optimize(compressor);
3801
3803
  }
3804
+
3805
+ if (self.expression instanceof AST_PropAccess
3806
+ && parent instanceof AST_PropAccess) {
3807
+ return self;
3808
+ }
3809
+
3802
3810
  let ev = self.evaluate(compressor);
3803
3811
  if (ev !== self) {
3804
3812
  ev = make_node_from_constant(ev, self).optimize(compressor);
package/lib/output.js CHANGED
@@ -1247,12 +1247,14 @@ function OutputStream(options) {
1247
1247
  output.with_indent(output.next_indent(), function() {
1248
1248
  output.append_comments(self, true);
1249
1249
  });
1250
+ output.add_mapping(self.end);
1250
1251
  output.print("}");
1251
1252
  }
1252
1253
  function print_braced(self, output, allow_directives) {
1253
1254
  if (self.body.length > 0) {
1254
1255
  output.with_block(function() {
1255
1256
  display_body(self.body, false, output, allow_directives);
1257
+ output.add_mapping(self.end);
1256
1258
  });
1257
1259
  } else print_braced_empty(self, output);
1258
1260
  }
@@ -249,7 +249,15 @@ function regexp_source_fix(source) {
249
249
  return (escaped ? "" : "\\") + lineTerminatorEscape[match];
250
250
  });
251
251
  }
252
- const all_flags = "gimuy";
252
+
253
+ // Subset of regexps that is not going to cause regexp based DDOS
254
+ // https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
255
+ const re_safe_regexp = /^[\\/|\0\s\w^$.[\]()]*$/;
256
+
257
+ /** Check if the regexp is safe for Terser to create without risking a RegExp DOS */
258
+ export const regexp_is_safe = (source) => re_safe_regexp.test(source);
259
+
260
+ const all_flags = "dgimsuy";
253
261
  function sort_regexp_flags(flags) {
254
262
  const existing_flags = new Set(flags.split(""));
255
263
  let out = "";
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "homepage": "https://terser.org",
5
5
  "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
6
6
  "license": "BSD-2-Clause",
7
- "version": "5.14.1",
7
+ "version": "5.14.2",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },