CVE-2024-4068 (published 2024-05-14T15:42Z)

The NPM package `braces`, versions prior to 3.0.3, fails to limit the number of characters it can handle, which could lead to Memory Exhaustion. In `lib/parse.js,` if a malicious user sends "imbalanced braces" as input, the parsing will enter a loop, which will cause the program to start allocating heap memory without freeing it at any moment of the loop. Eventually, the JavaScript heap limit is reached, and the program will crash.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/micromatch/braces
commit_details: repo_url: https://github.com/micromatch/braces
author: Aaron Moat
author_date: 2024-05-21 17:42:30+10:00
msg: Snyk js braces 6838727 (#40)

* Remove maxSymbols from README

* Revert "Merge pull request #37 from coderaiser/fix/vulnerability"

This reverts commit a5851e57f45c3431a94d83fc565754bc10f5bbc3, reversing
changes made to 98414f9f1fabe021736e26836d8306d5de747e0d.

* Lower defaultLength to 10000
repository: repo_url: https://github.com/micromatch/braces
repo_name: micromatch/braces
description: Faster brace expansion for node.js. Besides being faster, braces is not subject to DoS attacks like minimatch, is more accurate, and has more complete support for Bash 4.3.
repo_language: JavaScript
owner: micromatch
file_changes: filename: .verb.md
change_type: ModificationType.MODIFY
diff: @@ -167,7 +167,7 @@ console.log(braces.expand('a{b}c'));
 
 **Type**: `Number`
 
-**Default**: `65,536`
+**Default**: `10,000`
 
 **Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera.
 

programming_language: Markdown
filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -178,7 +178,7 @@ console.log(braces.expand('a{b}c'));
 
 **Type**: `Number`
 
-**Default**: `65,536`
+**Default**: `10,000`
 
 **Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera.
 
@@ -186,18 +186,6 @@ console.log(braces.expand('a{b}c'));
 console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error
 ```
 
-### options.maxSymbols
-
-**Type**: `Number`
-
-**Default**: `1024`
-
-**Description**: Limit the count of unique symbols the input string.
-
-```js
-console.log(braces('a/{b,c}/d', { maxSymbols: 2 })); //=> throws an error
-```
-
 ### options.expand
 
 **Type**: `Boolean`

programming_language: Markdown
filename: constants.js
change_type: ModificationType.MODIFY
diff: @@ -1,8 +1,7 @@
 'use strict';
 
 module.exports = {
-  MAX_LENGTH: 1024 * 64,
-  MAX_SYMBOLS: 1024,
+  MAX_LENGTH: 10000,
 
   // Digits
   CHAR_0: '0', /* 0 */

programming_language: JavaScript
filename: parse.js
change_type: ModificationType.MODIFY
diff: @@ -1,7 +1,6 @@
 'use strict';
 
 const stringify = require('./stringify');
-const {isCorrectBraces, validateInput} = require('./validate-input');
 
 /**
  * Constants
@@ -9,7 +8,6 @@ const {isCorrectBraces, validateInput} = require('./validate-input');
 
 const {
   MAX_LENGTH,
-  MAX_SYMBOLS,
   CHAR_BACKSLASH, /* \ */
   CHAR_BACKTICK, /* ` */
   CHAR_COMMA, /* , */
@@ -36,11 +34,6 @@ const parse = (input, options = {}) => {
   }
 
   let opts = options || {};
-
-  validateInput(input, {
-    maxSymbols: opts.maxSymbols || MAX_SYMBOLS,
-  });
-
   let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH;
   if (input.length > max) {
     throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`);
@@ -311,43 +304,30 @@ const parse = (input, options = {}) => {
     push({ type: 'text', value });
   }
 
-  flattenBlocks(stack)
-  markImbalancedBraces(ast);
-  push({ type: 'eos' });
-
-  return ast;
-};
-
-module.exports = parse;
-
-function markImbalancedBraces({nodes}) {
   // Mark imbalanced braces and brackets as invalid
-  for (const node of nodes) {
-    if (!node.nodes && !node.invalid) {
-      if (node.type === 'open') node.isOpen = true;
-      if (node.type === 'close') node.isClose = true;
-      if (!node.nodes) node.type = 'text';
-
-      node.invalid = true;
-    }
-
-    delete node.parent;
-    delete node.prev;
-  }
-}
-
-function flattenBlocks(stack) {
-  let block;
   do {
     block = stack.pop();
 
-    if (block.type === 'root')
-      continue;
+    if (block.type !== 'root') {
+      block.nodes.forEach(node => {
+        if (!node.nodes) {
+          if (node.type === 'open') node.isOpen = true;
+          if (node.type === 'close') node.isClose = true;
+          if (!node.nodes) node.type = 'text';
+          node.invalid = true;
+        }
+      });
 
-    // get the location of the block on parent.nodes (block's siblings)
-    let parent = stack.at(-1);
-    let index = parent.nodes.indexOf(block);
-    // replace the (invalid) block with its nodes
-    parent.nodes.splice(index, 1, ...block.nodes);
+      // get the location of the block on parent.nodes (block's siblings)
+      let parent = stack[stack.length - 1];
+      let index = parent.nodes.indexOf(block);
+      // replace the (invalid) block with it's nodes
+      parent.nodes.splice(index, 1, ...block.nodes);
+    }
   } while (stack.length > 0);
-}
+
+  push({ type: 'eos' });
+  return ast;
+};
+
+module.exports = parse;

programming_language: JavaScript
filename: validate-input.js
change_type: ModificationType.DELETE
diff: @@ -1,12 +0,0 @@
-module.exports.validateInput = (line, {maxSymbols}) => {
-  const symbols = {};
-
-    for (const current of line) {
-        symbols[current] = (symbols[current] || 0) + 1;
-    }
-
-    for (const [value, count] of Object.entries(symbols)) {
-      if (count > maxSymbols)
-        throw SyntaxError(`To many symbols '${value}'. Maximum: ${maxSymbols} allowed. Received: ${count}`);
-    }
-};

programming_language: unknown
filename: braces.parse.js
change_type: ModificationType.MODIFY
diff: @@ -10,16 +10,6 @@ describe('braces.parse()', () => {
       let MAX_LENGTH = 1024 * 64;
       assert.throws(() => parse('.'.repeat(MAX_LENGTH + 2)));
     });
-    it('should throw an error when symbols exceeds max symbols count default', () => {
-      let SYMBOLS= 1024;
-      assert.throws(() => parse('.'.repeat(MAX_SYMBOLS * 2)));
-    });
-    it('should throw an error when symbols exceeds max symbols count ', () => {
-      let SYMBOLS= 2;
-      assert.throws(() => parse('...', {
-        maxSymbols: 2,
-      }));
-    });
   });
 
   describe('valid', () => {

programming_language: JavaScript