@@ -43,21 +43,25 @@ const FRAMEWORK_FILTERS: Record<
4343 if ( payload . entryType === 'test' && payload . fullTitle ) {
4444 // Cucumber fullTitle format: "1: Scenario name" or "2: Scenario name"
4545 // Extract the row number and scenario name
46- // Use non-greedy match and avoid catastrophic backtracking
47- const rowMatch = payload . fullTitle . match ( / ^ ( \d + ) : \s * ( .* ) $ / )
48- if ( rowMatch ) {
49- const [ , rowNumber , scenarioName ] = rowMatch
50- // Use spec file filter
51- if ( specArg ) {
52- filters . push ( '--spec' , specArg )
46+ // Avoid ReDoS by removing ambiguous \s* before .* - use string operations instead
47+ const colonIndex = payload . fullTitle . indexOf ( ':' )
48+ if ( colonIndex > 0 ) {
49+ const rowNumber = payload . fullTitle . substring ( 0 , colonIndex )
50+ const scenarioName = payload . fullTitle . substring ( colonIndex + 1 ) . trim ( )
51+ // Validate row number is digits only
52+ if ( / ^ \d + $ / . test ( rowNumber ) ) {
53+ // Use spec file filter
54+ if ( specArg ) {
55+ filters . push ( '--spec' , specArg )
56+ }
57+ // Use regex to match the exact "rowNumber: scenarioName" pattern
58+ // This ensures we only run that specific example row
59+ filters . push (
60+ '--cucumberOpts.name' ,
61+ `^${ rowNumber } :\\s*${ escapeRegex ( scenarioName ) } $`
62+ )
63+ return filters
5364 }
54- // Use regex to match the exact "rowNumber: scenarioName" pattern
55- // This ensures we only run that specific example row
56- filters . push (
57- '--cucumberOpts.name' ,
58- `^${ rowNumber } :\\s*${ escapeRegex ( scenarioName . trim ( ) ) } $`
59- )
60- return filters
6165 }
6266 // No row number - use plain name filter
6367 if ( specArg ) {
0 commit comments