@@ -32,6 +32,7 @@ pub struct Spec {
3232
3333struct Executor {
3434 root_path : PathBuf ,
35+ canonical_root_path : Option < PathBuf > ,
3536 binary : bool ,
3637 pattern_matcher : PatternMatcher ,
3738 max_file_size : Option < i64 > ,
@@ -134,14 +135,60 @@ impl SourceExecutor for Executor {
134135 options : & SourceExecutorReadOptions ,
135136 ) -> Result < PartialSourceRowData > {
136137 let path = key. single_part ( ) ?. str_value ( ) ?. as_ref ( ) ;
137- if !self . pattern_matcher . is_file_included ( path) {
138+ let path_obj = Path :: new ( path) ;
139+
140+ // Prevent path traversal vulnerabilities by verifying the path
141+ // doesn't contain parent directory or absolute components.
142+ if path_obj. components ( ) . any ( |c| {
143+ matches ! (
144+ c,
145+ std:: path:: Component :: ParentDir
146+ | std:: path:: Component :: RootDir
147+ | std:: path:: Component :: Prefix ( _)
148+ )
149+ } ) || !self . pattern_matcher . is_file_included ( path)
150+ {
138151 return Ok ( PartialSourceRowData {
139152 value : Some ( SourceValue :: NonExistence ) ,
140153 ordinal : Some ( Ordinal :: unavailable ( ) ) ,
141154 content_version_fp : None ,
142155 } ) ;
143156 }
157+
144158 let path = self . root_path . join ( path) ;
159+
160+ // Mitigate symlink-based path traversal by canonicalizing and checking boundaries
161+ if let Some ( root_canon) = & self . canonical_root_path {
162+ let path_canon = match tokio:: fs:: canonicalize ( & path) . await {
163+ Ok ( c) => c,
164+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: NotFound => {
165+ // Target file doesn't exist.
166+ return Ok ( PartialSourceRowData {
167+ value : Some ( SourceValue :: NonExistence ) ,
168+ ordinal : Some ( Ordinal :: unavailable ( ) ) ,
169+ content_version_fp : None ,
170+ } ) ;
171+ }
172+ Err ( e) => Err ( e) ?,
173+ } ;
174+
175+ if !path_canon. starts_with ( root_canon) {
176+ // Symlink points outside the allowed root directory.
177+ return Ok ( PartialSourceRowData {
178+ value : Some ( SourceValue :: NonExistence ) ,
179+ ordinal : Some ( Ordinal :: unavailable ( ) ) ,
180+ content_version_fp : None ,
181+ } ) ;
182+ }
183+ } else {
184+ // Root doesn't exist (failed to canonicalize during setup), so the file cannot exist.
185+ return Ok ( PartialSourceRowData {
186+ value : Some ( SourceValue :: NonExistence ) ,
187+ ordinal : Some ( Ordinal :: unavailable ( ) ) ,
188+ content_version_fp : None ,
189+ } ) ;
190+ }
191+
145192 let mut metadata: Option < Metadata > = None ;
146193 // Check file size limit
147194 if let Some ( max_size) = self . max_file_size
@@ -237,8 +284,12 @@ impl SourceFactoryBase for Factory {
237284 spec : Spec ,
238285 _context : Arc < FlowInstanceContext > ,
239286 ) -> Result < Box < dyn SourceExecutor > > {
287+ let root_path = PathBuf :: from ( spec. path ) ;
288+ let canonical_root_path = tokio:: fs:: canonicalize ( & root_path) . await . ok ( ) ;
289+
240290 Ok ( Box :: new ( Executor {
241- root_path : PathBuf :: from ( spec. path ) ,
291+ root_path,
292+ canonical_root_path,
242293 binary : spec. binary ,
243294 pattern_matcher : PatternMatcher :: new ( spec. included_patterns , spec. excluded_patterns ) ?,
244295 max_file_size : spec. max_file_size ,
0 commit comments