@@ -31,7 +31,13 @@ async function _getRuleFromUrl(url) {
3131 return url . endsWith ( urlFragment ) ;
3232 case 'REGEX' :
3333 case 'REGEXP' :
34- return new RegExp ( urlFragment ) . test ( url ) ;
34+ try {
35+ const regex = createSafeRegex ( urlFragment ) ;
36+ return regex . test ( url ) ;
37+ } catch ( e ) {
38+ console . error ( 'Error processing regex pattern for URL matching:' , e ) ;
39+ return false ;
40+ }
3541 case 'EXACT' :
3642 return url === urlFragment ;
3743 default :
@@ -52,6 +58,37 @@ export function updateTitle(title, tag, value) {
5258 return value ? title . replace ( tag , decodeURI ( value ) ) : title ;
5359}
5460
61+ function isRegexSafe ( pattern ) {
62+ // Basic validation to prevent ReDoS attacks
63+ if ( typeof pattern !== 'string' || pattern . length > 200 ) {
64+ return false ;
65+ }
66+
67+ // Check for potentially dangerous patterns that can cause ReDoS
68+ const dangerousPatterns = [
69+ / \( \? \= .* \) \+ / , // Positive lookahead with quantifiers
70+ / \( \? \! .* \) \+ / , // Negative lookahead with quantifiers
71+ / \( .+ \) \+ \$ / , // Catastrophic backtracking patterns
72+ / \( .+ \) \* \+ / , // Conflicting quantifiers
73+ / \( \. \* \) \{ 2 , \} / , // Multiple .* in groups
74+ / \( \. \+ \) \{ 2 , \} / , // Multiple .+ in groups
75+ ] ;
76+
77+ return ! dangerousPatterns . some ( dangerous => dangerous . test ( pattern ) ) ;
78+ }
79+
80+ function createSafeRegex ( pattern , flags = 'g' ) {
81+ if ( ! isRegexSafe ( pattern ) ) {
82+ throw new Error ( 'Potentially unsafe regex pattern detected' ) ;
83+ }
84+
85+ try {
86+ return new RegExp ( pattern , flags ) ;
87+ } catch ( e ) {
88+ throw new Error ( `Invalid regex pattern: ${ e . message } ` ) ;
89+ }
90+ }
91+
5592export function getTextBySelector ( selector ) {
5693 let el = null ;
5794
@@ -61,7 +98,7 @@ export function getTextBySelector(selector) {
6198 const toSafe = ( s ) =>
6299 typeof CSS !== 'undefined' && CSS . escape
63100 ? CSS . escape ( s )
64- : s . replace ( / [ \" ] / g, '$& ' ) . replace ( / ] / g, '\\]' ) ;
101+ : s . replace ( / \\ / g , '\\\\' ) . replace ( / " / g, '\\" ' ) . replace ( / ] / g, '\\]' ) ;
65102
66103 const modifiedParts = parts . map ( ( part ) => {
67104 if ( ! part . includes ( '*' ) ) return part ;
@@ -124,35 +161,41 @@ export function processTitle(currentUrl, currentTitle, rule) {
124161
125162 if ( rule . tab . title_matcher ) {
126163 try {
127- const regex = new RegExp ( rule . tab . title_matcher , 'g' ) ;
164+ const regex = createSafeRegex ( rule . tab . title_matcher , 'g' ) ;
128165 let matches ;
129166 let i = 0 ;
167+ let iterationCount = 0 ;
168+ const maxIterations = 100 ; // Prevent infinite loops
130169
131- while ( ( matches = regex . exec ( currentTitle ) ) !== null ) {
170+ while ( ( matches = regex . exec ( currentTitle ) ) !== null && iterationCount < maxIterations ) {
132171 for ( let j = 0 ; j < matches . length ; j ++ ) {
133172 title = updateTitle ( title , '@' + i , matches [ j ] ) ;
134173 i ++ ;
135174 }
175+ iterationCount ++ ;
136176 }
137177 } catch ( e ) {
138- console . error ( e ) ;
178+ console . error ( 'Error processing title_matcher regex:' , e ) ;
139179 }
140180 }
141181
142182 if ( rule . tab . url_matcher ) {
143183 try {
144- const regex = new RegExp ( rule . tab . url_matcher , 'g' ) ;
184+ const regex = createSafeRegex ( rule . tab . url_matcher , 'g' ) ;
145185 let matches ;
146186 let i = 0 ;
187+ let iterationCount = 0 ;
188+ const maxIterations = 100 ; // Prevent infinite loops
147189
148- while ( ( matches = regex . exec ( currentUrl ) ) !== null ) {
190+ while ( ( matches = regex . exec ( currentUrl ) ) !== null && iterationCount < maxIterations ) {
149191 for ( let j = 0 ; j < matches . length ; j ++ ) {
150192 title = updateTitle ( title , '$' + i , matches [ j ] ) ;
151193 i ++ ;
152194 }
195+ iterationCount ++ ;
153196 }
154197 } catch ( e ) {
155- console . error ( e ) ;
198+ console . error ( 'Error processing url_matcher regex:' , e ) ;
156199 }
157200 }
158201
0 commit comments