From 8ec26f4a95eaeec69c42820025e48028b2130414 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Tue, 21 Jul 2026 13:58:55 +0000 Subject: [PATCH] fix: avoid catastrophic backtracking in number parsing for empty thousand separator (DEV-2120) When thousandSeparator is empty (the default), the number-detection pattern's group `(sep\d{3,})*` degenerated to `(\d{3,})*` adjacent to `\d+`, producing catastrophic regex backtracking (ReDoS) on a long run of digits ending in a non-digit character. Entering such a value into a cell froze the page. Omit the thousand-separator group entirely when the separator is empty. Behavior for non-empty separators is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + src/NumberLiteralHelper.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94424f8a3..783f0a67f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fixed the behavior of `MATCH`, `VLOOKUP`, `HLOOKUP`, and `XLOOKUP` functions when the search range contained empty cells. [#1697](https://github.com/handsontable/hyperformula/pull/1697) - Fixed the `VLOOKUP`, `HLOOKUP`, and `XLOOKUP` functions to return `0` instead of an empty value when the matched cell in the result range is empty. [#1697](https://github.com/handsontable/hyperformula/pull/1697) +- Fixed the page freezing when entering a long string of digits ending in a non-digit character (e.g. `012...789a`) into a cell, caused by catastrophic regex backtracking in number parsing. [#1713](https://github.com/handsontable/hyperformula/pull/1713) ## [3.3.0] - 2026-05-20 diff --git a/src/NumberLiteralHelper.ts b/src/NumberLiteralHelper.ts index 8d7a526ce..bf03e7796 100644 --- a/src/NumberLiteralHelper.ts +++ b/src/NumberLiteralHelper.ts @@ -16,7 +16,11 @@ export class NumberLiteralHelper { const thousandSeparator = this.config.thousandSeparator === '.' ? `\\${this.config.thousandSeparator}` : this.config.thousandSeparator const decimalSeparator = this.config.decimalSeparator === '.' ? `\\${this.config.decimalSeparator}` : this.config.decimalSeparator - this.numberPattern = new RegExp(`^([+-]?((${decimalSeparator}\\d+)|(\\d+(${thousandSeparator}\\d{3,})*(${decimalSeparator}\\d*)?)))([eE][+-]?\\d+)?$`) + // When thousandSeparator is empty (the default), the group `(sep\d{3,})*` degenerates to + // `(\d{3,})*` adjacent to `\d+`, which causes catastrophic backtracking (ReDoS) on a long + // run of digits that ultimately fails to match. Omit the group entirely in that case. (DEV-2120) + const thousandSeparatorGroup = this.config.thousandSeparator === '' ? '' : `(${thousandSeparator}\\d{3,})*` + this.numberPattern = new RegExp(`^([+-]?((${decimalSeparator}\\d+)|(\\d+${thousandSeparatorGroup}(${decimalSeparator}\\d*)?)))([eE][+-]?\\d+)?$`) this.allThousandSeparatorsRegex = new RegExp(`${thousandSeparator}`, 'g') }