@@ -17,10 +17,12 @@ const readFileAsync = promisify(readFile);
1717 * Accepts Sentry formatted stack frames and
1818 * adds source context to the in app frames.
1919 *
20- * Filenames are resolved relative to `projectRoot` and must remain within it.
20+ * Relative filenames are resolved against the first entry in `allowedRoots`.
21+ * A resolved filename must be contained in at least one of `allowedRoots`
22+ * (project root plus any Metro `watchFolders`).
2123 */
22- export const createStackFramesContextMiddleware = ( projectRoot : string ) : Middleware => {
23- const normalizedRoot = path . resolve ( projectRoot ) ;
24+ export const createStackFramesContextMiddleware = ( allowedRoots : string [ ] ) : Middleware => {
25+ const normalizedRoots = allowedRoots . map ( root => path . resolve ( root ) ) ;
2426
2527 return async ( request : IncomingMessage , response : ServerResponse , _next : ( ) => void ) : Promise < void > => {
2628 debug . log ( '[@sentry/react-native/metro] Received request for stack frames context.' ) ;
@@ -45,15 +47,15 @@ export const createStackFramesContextMiddleware = (projectRoot: string): Middlew
4547 return ;
4648 }
4749
48- const stackWithSourceContext = await Promise . all ( stack . map ( frame => addSourceContext ( frame , normalizedRoot ) ) ) ;
50+ const stackWithSourceContext = await Promise . all ( stack . map ( frame => addSourceContext ( frame , normalizedRoots ) ) ) ;
4951 response . setHeader ( 'Content-Type' , 'application/json' ) ;
5052 response . statusCode = 200 ;
5153 response . end ( JSON . stringify ( { stack : stackWithSourceContext } ) ) ;
5254 debug . log ( '[@sentry/react-native/metro] Sent stack frames context.' ) ;
5355 } ;
5456} ;
5557
56- async function addSourceContext ( frame : StackFrame , projectRoot : string ) : Promise < StackFrame > {
58+ async function addSourceContext ( frame : StackFrame , allowedRoots : string [ ] ) : Promise < StackFrame > {
5759 if ( ! frame . in_app ) {
5860 return frame ;
5961 }
@@ -64,10 +66,18 @@ async function addSourceContext(frame: StackFrame, projectRoot: string): Promise
6466 return frame ;
6567 }
6668
67- const resolvedPath = path . resolve ( projectRoot , frame . filename ) ;
68- const relative = path . relative ( projectRoot , resolvedPath ) ;
69- if ( relative === '' || relative . startsWith ( '..' ) || path . isAbsolute ( relative ) ) {
70- debug . warn ( '[@sentry/react-native/metro] Skipping frame whose filename is outside the project root.' ) ;
69+ if ( allowedRoots . length === 0 ) {
70+ debug . warn ( '[@sentry/react-native/metro] Skipping frame: no allowed roots configured.' ) ;
71+ return frame ;
72+ }
73+
74+ const resolvedPath = path . resolve ( allowedRoots [ 0 ] ! , frame . filename ) ;
75+ const isInside = allowedRoots . some ( root => {
76+ const relative = path . relative ( root , resolvedPath ) ;
77+ return relative !== '' && ! relative . startsWith ( '..' ) && ! path . isAbsolute ( relative ) ;
78+ } ) ;
79+ if ( ! isInside ) {
80+ debug . warn ( '[@sentry/react-native/metro] Skipping frame whose filename is outside the allowed roots.' ) ;
7181 return frame ;
7282 }
7383
@@ -88,8 +98,8 @@ function badRequest(response: ServerResponse, message: string): void {
8898/**
8999 * Creates a middleware that adds source context to the Sentry formatted stack frames.
90100 */
91- export const createSentryMetroMiddleware = ( middleware : Middleware , projectRoot : string ) : Middleware => {
92- const stackFramesContextMiddleware = createStackFramesContextMiddleware ( projectRoot ) as (
101+ export const createSentryMetroMiddleware = ( middleware : Middleware , allowedRoots : string [ ] ) : Middleware => {
102+ const stackFramesContextMiddleware = createStackFramesContextMiddleware ( allowedRoots ) as (
93103 req : IncomingMessage ,
94104 res : ServerResponse ,
95105 next : ( ) => void ,
@@ -117,10 +127,14 @@ export const withSentryMiddleware = (config: InputConfigT): InputConfigT => {
117127 config . server = { } ;
118128 }
119129
120- const projectRoot = ( config as { projectRoot ?: string } ) . projectRoot || process . cwd ( ) ;
130+ const typedConfig = config as { projectRoot ?: string ; watchFolders ?: readonly string [ ] } ;
131+ const projectRoot = typedConfig . projectRoot || process . cwd ( ) ;
132+ const watchFolders = typedConfig . watchFolders || [ ] ;
133+ const allowedRoots = [ projectRoot , ...watchFolders ] ;
134+
121135 const originalEnhanceMiddleware = config . server . enhanceMiddleware ;
122136 config . server . enhanceMiddleware = ( middleware , server ) => {
123- const sentryMiddleware = createSentryMetroMiddleware ( middleware , projectRoot ) ;
137+ const sentryMiddleware = createSentryMetroMiddleware ( middleware , allowedRoots ) ;
124138 return originalEnhanceMiddleware ? originalEnhanceMiddleware ( sentryMiddleware , server ) : sentryMiddleware ;
125139 } ;
126140 return config ;
0 commit comments