CVE-2024-4068 (JavaScript, published 2024-05-14T15:42Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/micromatch/braces
Files changed:
.verb.md
README.md
lib/constants.js
lib/parse.js
test/braces.parse.js
Commit message: 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

Vulnerable code:
**Default**: `65,536`
**Default**: `65,536`
### 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
```

  MAX_LENGTH: 1024 * 64,
  MAX_SYMBOLS: 1024,
const {isCorrectBraces, validateInput} = require('./validate-input');
  MAX_SYMBOLS,

  validateInput(input, {
    maxSymbols: opts.maxSymbols || MAX_SYMBOLS,
  });

  flattenBlocks(stack)
  markImbalancedBraces(ast);
  push({ type: 'eos' });

  return ast;
};

module.exports = parse;

function markImbalancedBraces({nodes}) {
  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;
    if (block.type === 'root')
      continue;
    // 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);
}
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}`);
    }
};
    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,
      }));
    });

Fixed code:
**Default**: `10,000`
**Default**: `10,000`
  MAX_LENGTH: 10000,
    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[stack.length - 1];
      let index = parent.nodes.indexOf(block);
      // replace the (invalid) block with it's nodes
      parent.nodes.splice(index, 1, ...block.nodes);
    }

  push({ type: 'eos' });
  return ast;
};

module.exports = parse;