@@ -1967,42 +1967,55 @@ func sendFileToDiscord(c *gin.Context) {
19671967 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] Sending to thread %s (instead of channel %s)" , threadID , channelID )
19681968 }
19691969
1970+ // Prevent Arbitrary File Read (Path Traversal)
1971+ cleanPath , err := filepath .Abs (req .FilePath )
1972+ if err != nil {
1973+ c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid file path" })
1974+ return
1975+ }
1976+ resultsDir , _ := filepath .Abs (utils .GetResultsDir ())
1977+ if ! strings .HasPrefix (cleanPath , resultsDir ) {
1978+ utils .GetLogger ().Warnf ("[API] [sendFileToDiscord] [SECURITY] Attempted to read file outside results directory: %s" , cleanPath )
1979+ c .JSON (http .StatusForbidden , gin.H {"error" : "file must be located within the results directory" })
1980+ return
1981+ }
1982+
19701983 // Check if file exists
1971- utils .GetLogger ().Infof ("[API] [sendFileToDiscord] Checking if file exists: %s" , req . FilePath )
1972- if info , err := os .Stat (req . FilePath ); os .IsNotExist (err ) {
1973- utils .GetLogger ().Infof ("[API] [sendFileToDiscord] [ERROR] File not found: %s" , req . FilePath )
1984+ utils .GetLogger ().Infof ("[API] [sendFileToDiscord] Checking if file exists: %s" , cleanPath )
1985+ if info , err := os .Stat (cleanPath ); os .IsNotExist (err ) {
1986+ utils .GetLogger ().Infof ("[API] [sendFileToDiscord] [ERROR] File not found: %s" , cleanPath )
19741987 c .JSON (http .StatusNotFound , gin.H {"error" : "file not found" })
19751988 return
19761989 } else if err != nil {
19771990 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] [ERROR] Failed to stat file: %v" , err )
19781991 c .JSON (http .StatusInternalServerError , gin.H {"error" : fmt .Sprintf ("failed to stat file: %v" , err )})
19791992 return
19801993 } else {
1981- utils .GetLogger ().Infof ("[API] [sendFileToDiscord] File found: %s (size: %d bytes)" , req . FilePath , info .Size ())
1994+ utils .GetLogger ().Infof ("[API] [sendFileToDiscord] File found: %s (size: %d bytes)" , cleanPath , info .Size ())
19821995 }
19831996
19841997 // Get file info
1985- fileName := filepath .Base (req . FilePath )
1998+ fileName := filepath .Base (cleanPath )
19861999 description := req .Description
19872000 if description == "" {
19882001 description = fmt .Sprintf ("📁 %s" , fileName )
19892002 }
19902003
19912004 // Get file info for size check
1992- fileInfo , err := os .Stat (req . FilePath )
2005+ fileInfo , err := os .Stat (cleanPath )
19932006 if err != nil {
19942007 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] [ERROR] Failed to stat file: %v" , err )
19952008 c .JSON (http .StatusInternalServerError , gin.H {"error" : fmt .Sprintf ("failed to stat file: %v" , err )})
19962009 return
19972010 }
19982011
19992012 // Check if file should use R2
2000- useR2 := r2storage .ShouldUseR2 (req . FilePath ) || (r2storage .IsEnabled () && fileInfo .Size () > r2storage .GetFileSizeLimit ())
2013+ useR2 := r2storage .ShouldUseR2 (cleanPath ) || (r2storage .IsEnabled () && fileInfo .Size () > r2storage .GetFileSizeLimit ())
20012014
20022015 if useR2 {
20032016 // Upload to R2 and send link
20042017 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] File is large (%d bytes), uploading to R2..." , fileInfo .Size ())
2005- publicURL , err := r2storage .UploadFile (req . FilePath , fileName , false )
2018+ publicURL , err := r2storage .UploadFile (cleanPath , fileName , false )
20062019 if err != nil {
20072020 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] [ERROR] Failed to upload to R2: %v" , err )
20082021 c .JSON (http .StatusInternalServerError , gin.H {"error" : fmt .Sprintf ("failed to upload to R2: %v" , err )})
@@ -2039,8 +2052,8 @@ func sendFileToDiscord(c *gin.Context) {
20392052 }
20402053
20412054 // #4: Stream file via os.Open instead of loading everything into RAM with os.ReadFile.
2042- utils .GetLogger ().Infof ("[API] [sendFileToDiscord] Opening file for streaming: %s" , req . FilePath )
2043- fileStream , streamErr := os .Open (req . FilePath )
2055+ utils .GetLogger ().Infof ("[API] [sendFileToDiscord] Opening file for streaming: %s" , cleanPath )
2056+ fileStream , streamErr := os .Open (cleanPath )
20442057 if streamErr != nil {
20452058 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] [ERROR] Failed to open file: %v" , streamErr )
20462059 c .JSON (http .StatusInternalServerError , gin.H {"error" : fmt .Sprintf ("failed to open file: %v" , streamErr )})
@@ -2087,7 +2100,7 @@ func sendFileToDiscord(c *gin.Context) {
20872100 if strings .Contains (err .Error (), "413" ) || strings .Contains (err .Error (), "too large" ) || strings .Contains (err .Error (), "Request entity too large" ) {
20882101 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] ⚠️ Discord upload failed due to size, uploading to R2 as fallback..." )
20892102 if r2storage .IsEnabled () {
2090- publicURL , r2Err := r2storage .UploadFile (req . FilePath , fileName , false )
2103+ publicURL , r2Err := r2storage .UploadFile (cleanPath , fileName , false )
20912104 if r2Err != nil {
20922105 utils .GetLogger ().Infof ("[API] [sendFileToDiscord] [ERROR] Failed to upload to R2: %v" , r2Err )
20932106 c .JSON (http .StatusInternalServerError , gin.H {"error" : fmt .Sprintf ("failed to send file and R2 upload failed: %v (R2 error: %v)" , err , r2Err )})
0 commit comments