Skip to content

Commit

Permalink
Merge pull request #37 from coderaiser/fix/vulnerability
Browse files Browse the repository at this point in the history
fix: vulnerability
  • Loading branch information
paulmillr authored May 17, 2024
2 parents 98414f9 + 2092bd1 commit a5851e5
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 21 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ 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`
Expand Down
1 change: 1 addition & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module.exports = {
MAX_LENGTH: 1024 * 64,
MAX_SYMBOLS: 1024,

// Digits
CHAR_0: '0', /* 0 */
Expand Down
62 changes: 41 additions & 21 deletions lib/parse.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
'use strict';

const stringify = require('./stringify');
const {isCorrectBraces, validateInput} = require('./validate-input');

/**
* Constants
*/

const {
MAX_LENGTH,
MAX_SYMBOLS,
CHAR_BACKSLASH, /* \ */
CHAR_BACKTICK, /* ` */
CHAR_COMMA, /* , */
Expand All @@ -34,6 +36,11 @@ 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})`);
Expand Down Expand Up @@ -304,30 +311,43 @@ 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') {
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;
}
});
if (block.type === 'root')
continue;

// 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);
}
// 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);
} while (stack.length > 0);

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

module.exports = parse;
}
12 changes: 12 additions & 0 deletions lib/validate-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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}`);
}
};
10 changes: 10 additions & 0 deletions test/braces.parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ 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', () => {
Expand Down

2 comments on commit a5851e5

@Gayatri-Chaudhary
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const { braces } = require('micromatch');

console.log("Executing payloads...");

const maxRepeats = 10;

for (let repeats = 1; repeats <= maxRepeats; repeats += 1) {
const payload = '{'.repeat(repeats*90000);

console.log(`Testing with ${repeats} repeats...`);
try{
  const startTime = Date.now();
  braces(payload);
  const endTime = Date.now();
 const executionTime = endTime - startTime;
  console.log(`Regex executed in ${executionTime / 1000}s.\n`);

}
catch(error){
console.error(Error with ${repeats} repeats:, error.message);
}
}

@Gayatri-Chaudhary
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use try-catch block to handle error

Please sign in to comment.