@@ -29,17 +29,18 @@ const DEFAULT_MAX_VALUE_LENGTH = 200;
2929const DEFAULT_MAX_ARRAY_ITEMS = 20 ;
3030const REGEX_FILTER_VALUE_SOURCE = String . raw `\/\/(?!\/\/(?:[\t\n )]|$))[^\n]{1,1024}?\/\/(?=[\t\n )]|$)` ;
3131const REGEX_FILTER_VALUE_PATTERN = new RegExp ( `^${ REGEX_FILTER_VALUE_SOURCE } ` ) ;
32- const SEARCH_FILTER_KEY_SOURCE = String . raw `(^|\s)!?([A-Za-z_][A-Za-z0-9_.[\],-]*):` ;
33- const REGEX_FILTER_KEY_SOURCE = String . raw `(^|[\s(])!?((?:tags|flags)\[[\w.:-]+(?: *, *(?:string|number|boolean|array))?\](?:\[\*\])?|"[\w.:-]+"(?:\[\*\])?|[A-Za-z_][A-Za-z0-9_.[\],-]*(?:\[\*\])?):` ;
32+ const SEARCH_FILTER_KEY_SOURCE = String . raw `(^|\s)!?(?<key> [A-Za-z_][A-Za-z0-9_.[\],-]*):` ;
33+ const REGEX_FILTER_KEY_SOURCE = String . raw `(^|[\s()" ])!?(?<regexKey> (?:tags|flags)\[[\w.:-]+(?: *, *(?:string|number|boolean|array))?\](?:\[\*\])?|"[\w.:-]+"(?:\[\*\])?|[A-Za-z_][A-Za-z0-9_.[\],-]*(?:\[\*\])?):` ;
3434const REGEX_FILTER_KEY_BEFORE_PATTERN = new RegExp (
3535 `${ REGEX_FILTER_KEY_SOURCE } $` ,
3636) ;
37+ const REGEX_FILTER_KEY_SCAN_LIMIT = 256 ;
3738const SENTRY_SEARCH_TOKEN_PATTERN = new RegExp (
3839 String . raw `${ SEARCH_FILTER_KEY_SOURCE } (?=\S)(?!\/\/)` ,
3940 "g" ,
4041) ;
4142const SENTRY_SEARCH_TOKEN_WITH_REGEX_PATTERN = new RegExp (
42- String . raw `${ SEARCH_FILTER_KEY_SOURCE } (?=\S)(?!\/\/)| ${ REGEX_FILTER_KEY_SOURCE } (?= ${ REGEX_FILTER_VALUE_SOURCE } )` ,
43+ String . raw `${ REGEX_FILTER_KEY_SOURCE } (?= ${ REGEX_FILTER_VALUE_SOURCE } )| ${ SEARCH_FILTER_KEY_SOURCE } (?=\S)(?!\/\/)` ,
4344 "g" ,
4445) ;
4546const KNOWN_SENTRY_SEARCH_KEYS = new Set ( [
@@ -117,11 +118,11 @@ export function looksLikeSentrySearchSyntax(
117118 }
118119
119120 for ( const match of trimmedQuery . matchAll ( searchTokenPattern ( dataset ) ) ) {
120- if ( match [ 4 ] ) {
121+ if ( match . groups ?. regexKey ) {
121122 return true ;
122123 }
123124
124- const key = match [ 2 ] ;
125+ const key = match . groups ?. key ;
125126 if ( ! key ) {
126127 continue ;
127128 }
@@ -150,10 +151,14 @@ export function readRegexFilterValue(
150151 query : string ,
151152 index : number ,
152153) : string | undefined {
153- if (
154- ! query . startsWith ( "//" , index ) ||
155- ! REGEX_FILTER_KEY_BEFORE_PATTERN . test ( query . slice ( 0 , index ) )
156- ) {
154+ if ( ! query . startsWith ( "//" , index ) || query [ index - 1 ] !== ":" ) {
155+ return undefined ;
156+ }
157+ // Callers probe every index, so only scan a bounded key-sized prefix; the
158+ // NUL stands in for truncated text so `^` can't match mid-query.
159+ const start = Math . max ( 0 , index - REGEX_FILTER_KEY_SCAN_LIMIT ) ;
160+ const prefix = `${ start > 0 ? "\0" : "" } ${ query . slice ( start , index ) } ` ;
161+ if ( ! REGEX_FILTER_KEY_BEFORE_PATTERN . test ( prefix ) ) {
157162 return undefined ;
158163 }
159164 return REGEX_FILTER_VALUE_PATTERN . exec ( query . slice ( index ) ) ?. [ 0 ] ;
@@ -275,7 +280,7 @@ function searchFilterOccurrences(
275280 const masked = maskQuotedRegions ( query ) ;
276281
277282 for ( const match of masked . matchAll ( searchTokenPattern ( dataset ) ) ) {
278- const key = ( match [ 2 ] ?? match [ 4 ] ) ?. toLowerCase ( ) ;
283+ const key = ( match . groups ?. regexKey ?? match . groups ?. key ) ?. toLowerCase ( ) ;
279284 if ( ! key || match . index === undefined ) {
280285 continue ;
281286 }
@@ -401,8 +406,13 @@ function isRegexFilterDowngrade(
401406 }
402407
403408 const repairedFilters = searchFilterOccurrences ( repairedQuery , dataset ) ;
409+ const maskedRepairedQuery = maskQuotedRegions ( repairedQuery ) ;
404410 return unmatchedStructuredFilters ( originalRegexFilters , repairedFilters ) . some (
405411 ( filter ) =>
412+ new RegExp (
413+ String . raw `(?:^|[\s()"])!?${ escapeRegExp ( filter . key ) } :(?!\/\/)` ,
414+ "i" ,
415+ ) . test ( maskedRepairedQuery ) ||
406416 repairedFilters . some (
407417 ( repaired ) =>
408418 repaired . key === filter . key ||
0 commit comments