@@ -9,9 +9,16 @@ const REGEX_INTENT = /[|()^$*+?[\]]/
99/** Maximum length for regex patterns to mitigate ReDoS from untrusted input. */
1010const MAX_REGEX_LENGTH = 200
1111
12- /** Timeout-safe regex test: compiles and tests the pattern, returning false for invalid or oversized patterns. */
12+ /** Detects nested quantifiers that can cause catastrophic backtracking, e.g. (a+)+, (a*)*, (\w+)+. */
13+ const NESTED_QUANTIFIER = / [ + * ] \) [ + * ? ] /
14+
15+ /**
16+ * Safe regex test: rejects patterns that are oversized, contain nested quantifiers
17+ * (the primary cause of catastrophic backtracking), or are syntactically invalid.
18+ */
1319function safeRegexTest ( pattern : string , value : string ) : boolean {
1420 if ( pattern . length > MAX_REGEX_LENGTH ) return false
21+ if ( NESTED_QUANTIFIER . test ( pattern ) ) return false
1522 try {
1623 return new RegExp ( pattern ) . test ( value )
1724 } catch {
@@ -22,44 +29,30 @@ function safeRegexTest(pattern: string, value: string): boolean {
2229/**
2330 * Check if a ground value matches a comparison value using the same matrix
2431 * as the UI's _showHideOperation in genericHelper.js.
32+ *
33+ * Ground values are normalized to arrays so scalar and array inputs share
34+ * a single code path, reducing duplication.
2535 */
2636export function conditionMatches ( groundValue : unknown , comparisonValue : unknown ) : boolean {
27- if ( Array . isArray ( groundValue ) ) {
28- if ( Array . isArray ( comparisonValue ) ) {
29- return comparisonValue . some ( ( val ) => ( groundValue as unknown [ ] ) . includes ( val ) )
30- }
31- if ( typeof comparisonValue === 'string' ) {
32- return ( groundValue as unknown [ ] ) . some ( ( val ) => {
33- if ( comparisonValue === val ) return true
34- if ( REGEX_INTENT . test ( comparisonValue ) ) {
35- return safeRegexTest ( comparisonValue , String ( val ) )
36- }
37- return false
38- } )
39- }
40- if ( typeof comparisonValue === 'boolean' || typeof comparisonValue === 'number' ) {
41- return ( groundValue as unknown [ ] ) . includes ( comparisonValue )
42- }
43- if ( typeof comparisonValue === 'object' && comparisonValue !== null ) {
44- return ( groundValue as unknown [ ] ) . some ( ( val ) => isEqual ( comparisonValue , val ) )
45- }
46- } else {
47- if ( Array . isArray ( comparisonValue ) ) {
48- return comparisonValue . includes ( groundValue )
49- }
50- if ( typeof comparisonValue === 'string' ) {
51- if ( comparisonValue === groundValue ) return true
37+ const groundArr : unknown [ ] = Array . isArray ( groundValue ) ? groundValue : [ groundValue ]
38+
39+ if ( Array . isArray ( comparisonValue ) ) {
40+ return comparisonValue . some ( ( val ) => groundArr . includes ( val ) )
41+ }
42+ if ( typeof comparisonValue === 'string' ) {
43+ return groundArr . some ( ( val ) => {
44+ if ( comparisonValue === val ) return true
5245 if ( REGEX_INTENT . test ( comparisonValue ) ) {
53- return safeRegexTest ( comparisonValue , String ( groundValue ) )
46+ return safeRegexTest ( comparisonValue , String ( val ) )
5447 }
5548 return false
56- }
57- if ( typeof comparisonValue === 'boolean' || typeof comparisonValue === 'number' ) {
58- return comparisonValue === groundValue
59- }
60- if ( typeof comparisonValue === 'object' && comparisonValue !== null ) {
61- return isEqual ( comparisonValue , groundValue )
62- }
49+ } )
50+ }
51+ if ( typeof comparisonValue === 'boolean' || typeof comparisonValue === 'number' ) {
52+ return groundArr . includes ( comparisonValue )
53+ }
54+ if ( typeof comparisonValue === 'object' && comparisonValue !== null ) {
55+ return groundArr . some ( ( val ) => isEqual ( comparisonValue , val ) )
6356 }
6457 return false
6558}
0 commit comments