CVE-2019-17592: Uncontrolled Resource Consumption (CWE-400)
value: The csv-parse module before 4.4.6 for Node.js is vulnerable to Regular Expression Denial of Service. The __isInt() function contains a malformed regular expression that processes large crafted input very slowly. This is triggered when using the cast option.
CWE description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
CVSS v3 base score: 7.5
Published: 2019-10-14T20:15Z
Repository: https://github.com/adaltas/node-csv-parse
Language: JavaScript
Files changed: CHANGELOG.md, lib/es5/index.js, lib/index.js, test/option.cast.coffee
Commit date: 2019-09-17 22:46:03 +0200
Commit message: security: remove regexp vulnerable to DOS in cast option, npm report 69742

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02eee8a..2198d72 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,10 +5,14 @@
 
 * skip_lines_with_error: rename to skip_records_with_error
 * max_comment_size: new option
 * promise: new API module
 
+## Trunk
+
+* security: remove regexp vulnerable to DOS in cast option, npm report 69742
+
 ## Version 4.4.5
 
 * ts: add buffer as allowed type for input, fix #248
 
 ## Version 4.4.4
diff --git a/lib/es5/index.js b/lib/es5/index.js
index 230d3ce..ebf61ce 100644
--- a/lib/es5/index.js
+++ b/lib/es5/index.js
@@ -983,25 +983,24 @@ function (_Transform) {
         } catch (err) {
           return [err];
         }
       }
 
-      if (this.__isInt(field) === true) {
-        return [undefined, parseInt(field)];
-      } else if (this.__isFloat(field)) {
+      if (this.__isFloat(field)) {
         return [undefined, parseFloat(field)];
       } else if (this.options.cast_date !== false) {
         return [undefined, this.options.cast_date.call(null, field, context)];
       }
 
       return [undefined, field];
-    }
-  }, {
-    key: "__isInt",
-    value: function __isInt(value) {
-      return /^(\-|\+)?([1-9]+[0-9]*)$/.test(value);
-    }
+    } // Keep it in case we implement the `cast_int` option
+    // __isInt(value){
+    //   // return Number.isInteger(parseInt(value))
+    //   // return !isNaN( parseInt( obj ) );
+    //   return /^(\-|\+)?[1-9][0-9]*$/.test(value)
+    // }
+
   }, {
     key: "__isFloat",
     value: function __isFloat(value) {
       return value - parseFloat(value) + 1 >= 0; // Borrowed from jquery
     }
diff --git a/lib/index.js b/lib/index.js
index dd296c8..a52e294 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -745,22 +745,23 @@ class Parser extends Transform {
         return [undefined, this.state.castField.call(null, field, context)]
       }catch(err){
         return [err]
       }
     }
-    if(this.__isInt(field) === true){
-      return [undefined, parseInt(field)]
-    }else if(this.__isFloat(field)){
+    if(this.__isFloat(field)){
       return [undefined, parseFloat(field)]
     }else if(this.options.cast_date !== false){
       return [undefined, this.options.cast_date.call(null, field, context)]
     }
     return [undefined, field]
   }
-  __isInt(value){
-    return /^(\-|\+)?([1-9]+[0-9]*)$/.test(value)
-  }
+  // Keep it in case we implement the `cast_int` option
+  // __isInt(value){
+  //   // return Number.isInteger(parseInt(value))
+  //   // return !isNaN( parseInt( obj ) );
+  //   return /^(\-|\+)?[1-9][0-9]*$/.test(value)
+  // }
   __isFloat(value){
     return (value - parseFloat( value ) + 1) >= 0 // Borrowed from jquery
   }
   __compareBytes(sourceBuf, targetBuf, pos, firtByte){
     if(sourceBuf[0] !== firtByte) return 0
diff --git a/test/option.cast.coffee b/test/option.cast.coffee
index 92fd564..c30135d 100644
--- a/test/option.cast.coffee
+++ b/test/option.cast.coffee
@@ -29,12 +29,18 @@ describe 'Option `cast`', ->
         ]
         next()
       parser.end()
 
     it 'ints', (next) ->
-      parse '123a,123,0123,', cast: true, (err, data) ->
-        data.should.eql [ ['123a', 123, 123, ''] ]
+      parse '123a,123,+123,-123,0123,+0123,-0123,', cast: true, (err, data) ->
+        data.should.eql [ ['123a', 123, 123, -123, 123, 123, -123, ''] ]
+        next()
+
+    it 'ints isnt exposed to DOS vulnerabilities, npm security issue 69742', (next) ->
+      data = Array.from( length: 3000000 ).map( (x) -> '1' ).join('') + '!'
+      parse data, cast: true, (err, data) ->
+        data[0][0].length.should.eql 3000001
         next()
 
     it 'float', (next) ->
       parse '123a,1.23,0.123,01.23,.123,123.', cast: true, (err, data) ->
         data.should.eql [ ['123a', 1.23, 0.123, 1.23, 0.123, 123] ]
@@ -52,15 +58,15 @@ describe 'Option `cast`', ->
           then "#{value}T05:00:00.000Z"
           else {...context}
       , (err, records) ->
         records.should.eql [
           [ '2000-01-01T05:00:00.000Z', {
-            column: 1, empty_lines: 0, header: false, index: 1, 
+            column: 1, empty_lines: 0, header: false, index: 1,
             invalid_field_length: 0, lines: 1, quoting: false, records: 0
           } ]
           [ '2050-11-27T05:00:00.000Z', {
-            column: 1, empty_lines: 0, header: false, index: 1, 
+            column: 1, empty_lines: 0, header: false, index: 1,
             invalid_field_length: 0, lines: 2, quoting: false, records: 1
           } ]
         ] unless err
         next err
 

Vulnerable code:       if (this.__isInt(field) === true) {
        return [undefined, parseInt(field)];
      } else if (this.__isFloat(field)) {
    }
  }, {
    key: "__isInt",
    value: function __isInt(value) {
      return /^(\-|\+)?([1-9]+[0-9]*)$/.test(value);
    }
    if(this.__isInt(field) === true){
      return [undefined, parseInt(field)]
    }else if(this.__isFloat(field)){
  __isInt(value){
    return /^(\-|\+)?([1-9]+[0-9]*)$/.test(value)
  }
      parse '123a,123,0123,', cast: true, (err, data) ->
        data.should.eql [ ['123a', 123, 123, ''] ]
            column: 1, empty_lines: 0, header: false, index: 1, 
            column: 1, empty_lines: 0, header: false, index: 1, 
Fixed code: ## Trunk

* security: remove regexp vulnerable to DOS in cast option, npm report 69742

      if (this.__isFloat(field)) {
    } // Keep it in case we implement the `cast_int` option
    // __isInt(value){
    //   // return Number.isInteger(parseInt(value))
    //   // return !isNaN( parseInt( obj ) );
    //   return /^(\-|\+)?[1-9][0-9]*$/.test(value)
    // }

    if(this.__isFloat(field)){
  // Keep it in case we implement the `cast_int` option
  // __isInt(value){
  //   // return Number.isInteger(parseInt(value))
  //   // return !isNaN( parseInt( obj ) );
  //   return /^(\-|\+)?[1-9][0-9]*$/.test(value)
  // }
      parse '123a,123,+123,-123,0123,+0123,-0123,', cast: true, (err, data) ->
        data.should.eql [ ['123a', 123, 123, -123, 123, 123, -123, ''] ]
        next()

    it 'ints isnt exposed to DOS vulnerabilities, npm security issue 69742', (next) ->
      data = Array.from( length: 3000000 ).map( (x) -> '1' ).join('') + '!'
      parse data, cast: true, (err, data) ->
        data[0][0].length.should.eql 3000001
            column: 1, empty_lines: 0, header: false, index: 1,
            column: 1, empty_lines: 0, header: false, index: 1,