VYPR
High severityNVD Advisory· Published May 13, 2024· Updated Nov 6, 2024

Memory Exhaustion in braces

CVE-2024-4068

Description

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.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Braces NPM package before 3.0.3 has a denial-of-service vulnerability due to memory exhaustion when parsing imbalanced braces input.

The NPM package braces, versions prior to 3.0.3, contains a denial-of-service (DoS) vulnerability stemming from improper input validation during brace expansion parsing. Specifically, in lib/parse.js, the parser fails to limit the length or complexity of the input string. When a malicious user supplies an imbalanced braces pattern (e.g., an excessive number of opening braces without corresponding closing braces), the parsing logic enters an infinite loop that continuously allocates heap memory without freeing it [1][2]. This leads to rapid memory exhaustion, eventually hitting the JavaScript heap limit and causing the Node.js process to crash.

To exploit this vulnerability, an attacker only needs to provide a crafted brace pattern to an application that uses the braces library to process user-supplied input. No authentication or special network position is required, making this a trivially exploitable attack vector for any service that exposes brace expansion to untrusted users. The library's default configuration prior to the fix did not impose strict limits on input length or symbol count, allowing an attacker to send a relatively small payload that triggers the exponential memory allocation loop.

The impact is a complete denial of service: the Node.js process terminates abruptly, potentially affecting all users of the application. Since the vulnerability is in a widely used npm package (over 100 million weekly downloads), many downstream projects are potentially affected. There is no evidence of remote code execution or data breach; the sole consequence is service unavailability.

The vulnerability has been addressed in version 3.0.3. The fix reduces the default maxLength option from 65,536 to 10,000 characters and removes the maxSymbols option altogether, enforcing stricter parsing limits [3]. Users are strongly advised to update to the latest version of braces immediately. No workarounds are available beyond input sanitization, which is error-prone.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
bracesnpm
< 3.0.33.0.3

Affected products

91

Patches

1
415d660c3002

Snyk js braces 6838727 (#40)

https://github.com/micromatch/bracesAaron MoatMay 21, 2024via ghsa
6 files changed · +24 79
  • lib/constants.js+1 2 modified
    @@ -1,8 +1,7 @@
     'use strict';
     
     module.exports = {
    -  MAX_LENGTH: 1024 * 64,
    -  MAX_SYMBOLS: 1024,
    +  MAX_LENGTH: 10000,
     
       // Digits
       CHAR_0: '0', /* 0 */
    
  • lib/parse.js+21 41 modified
    @@ -1,15 +1,13 @@
     'use strict';
     
     const stringify = require('./stringify');
    -const {isCorrectBraces, validateInput} = require('./validate-input');
     
     /**
      * Constants
      */
     
     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;
    
  • lib/validate-input.js+0 12 removed
    @@ -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}`);
    -    }
    -};
    
  • README.md+1 13 modified
    @@ -178,26 +178,14 @@ 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.
     
     ```js
     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`
    
  • test/braces.parse.js+0 10 modified
    @@ -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', () => {
    
  • .verb.md+1 1 modified
    @@ -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.
     
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.