2424)
2525
2626UPLOADS_ROOT = Path ("/app/uploads" )
27+ ALLOWED_SUFFIXES = {".jpg" , ".jpeg" , ".png" , ".mp4" }
28+
29+ # Fix #4: ensure uploads directory exists at startup
30+ UPLOADS_ROOT .mkdir (parents = True , exist_ok = True )
2731
2832app = FastAPI ()
2933
@@ -48,20 +52,34 @@ def process(payload: dict):
4852 logger .warning ("Missing minio_object in payload" , extra = {"status" : "error" })
4953 return {"error" : "minio_object is required" }
5054
51- ext = Path (minio_object ).suffix .lower () or ".jpg"
55+ # Fix #1: only extract the suffix from user input — never use minio_object
56+ # as a path component directly, preventing path traversal via object keys
57+ ext = Path (minio_object ).suffix .lower ()
58+ if ext not in ALLOWED_SUFFIXES :
59+ logger .warning (
60+ f"Rejected unsupported extension — { ext !r} " ,
61+ extra = {"status" : "error" }
62+ )
63+ return {"error" : "Unsupported file type" }
64+
5265 filename = f"{ uuid .uuid4 ()} { ext } "
53- tmp_path = str (UPLOADS_ROOT / filename )
66+
67+ # Fix #2: initialise tmp_path before try so finally block never hits NameError
68+ # Fix #3: keep as Path throughout — only cast to str at call sites that need it
69+ tmp_path : Path | None = None
5470
5571 try :
72+ tmp_path = UPLOADS_ROOT / filename
73+
5674 logger .info (
5775 f"Downloading from MinIO — object={ minio_object } " ,
5876 extra = {"status" : "called" }
5977 )
60- minio_client .fget_object (MINIO_BUCKET , minio_object , tmp_path )
78+ minio_client .fget_object (MINIO_BUCKET , minio_object , str ( tmp_path ) )
6179 logger .info ("MinIO download complete" , extra = {"status" : "success" })
6280
6381 logger .info ("Preprocessing invoked" , extra = {"status" : "called" })
64- frames , preprocessing_signal = preprocess (tmp_path )
82+ frames , preprocessing_signal = preprocess (str ( tmp_path ) )
6583 logger .info (
6684 f"Preprocessing complete — quality={ preprocessing_signal .score } " ,
6785 extra = {"status" : "success" }
@@ -81,8 +99,9 @@ def process(payload: dict):
8199 logger .exception ("Unexpected error during processing" , extra = {"status" : "error" })
82100 return {"error" : "Internal processing error" }
83101 finally :
84- if os .path .exists (tmp_path ):
85- os .unlink (tmp_path )
102+ # Fix #2+3: tmp_path is a Path or None — .exists()/.unlink() are safe either way
103+ if tmp_path is not None and tmp_path .exists ():
104+ tmp_path .unlink ()
86105 logger .info ("Temp file cleaned up" , extra = {"status" : "success" })
87106
88107 logger .info (
@@ -94,4 +113,5 @@ def process(payload: dict):
94113 "record_id" : record_id ,
95114 "preprocessing" : preprocessing_signal .model_dump (),
96115 "detection" : detection_signal .model_dump (),
97- }
116+ }
117+
0 commit comments