@cryptotaxi247 / CoPilot / commits / 95b08877

789 unauthenticated file upload (#790)

* fix: add authentication dependency to file upload endpoint * fix: refactor file upload to use secure temporary storage * precommit-fixes

taylor_socfortress committed Apr 2, 2026 at 14:48 UTC 95b088779361b5aed101a992dd783fd7ba6dc066
2 files changed +66 -72
backend/app/data_store/data_store_operations.py
+65 -72
@@ -1,8 +1,8 @@
1 import hashlib
2 import os
3 +import tempfile
4 from typing import Optional
5
5 -import aiofiles
6 import aiohttp
7 from fastapi import HTTPException
8 from fastapi import UploadFile
@@ -26,48 +26,44 @@ async def upload_case_data_store(data: CaseDataStoreCreation, file: UploadFile)
26 client = await create_session()
27 logger.info(f"Uploading file {file.filename} to bucket {data.bucket_name}")
28
29 - # Define the temporary file path
30 - temp_file_path = os.path.join(os.getcwd(), file.filename)
31 -
32 - # Save the file to the temporary location
33 - async with aiofiles.open(temp_file_path, "wb") as out_file:
29 + # Save the file to a secure temporary location
30 + with tempfile.NamedTemporaryFile(delete=False, dir="/tmp") as tmp:
31 content = await file.read()
35 - await out_file.write(content)
32 + tmp.write(content)
33 + temp_file_path = tmp.name
34
37 - # Upload the file to Minio
38 - await client.fput_object(
39 - bucket_name=data.bucket_name,
40 - object_name=f"{data.case_id}/{file.filename}",
41 - file_path=temp_file_path,
42 - content_type=data.content_type,
43 - )
44 -
45 - # Optionally, remove the temporary file after upload
46 - os.remove(temp_file_path)
35 + try:
36 + await client.fput_object(
37 + bucket_name=data.bucket_name,
38 + object_name=f"{data.case_id}/{file.filename}",
39 + file_path=temp_file_path,
40 + content_type=data.content_type,
41 + )
42 + finally:
43 + if os.path.exists(temp_file_path):
44 + os.remove(temp_file_path)
45
46
47 async def upload_case_report_template_data_store(data: CaseReportTemplateDataStoreCreation, file: UploadFile) -> None:
48 client = await create_session()
49 logger.info(f"Uploading file {file.filename} to bucket {data.bucket_name}")
50
53 - # Define the temporary file path
54 - temp_file_path = os.path.join(os.getcwd(), file.filename)
55 -
56 - # Save the file to the temporary location
57 - async with aiofiles.open(temp_file_path, "wb") as out_file:
51 + # Save the file to a secure temporary location
52 + with tempfile.NamedTemporaryFile(delete=False, dir="/tmp") as tmp:
53 content = await file.read()
59 - await out_file.write(content)
60 -
61 - # Upload the file to Minio
62 - await client.fput_object(
63 - bucket_name=data.bucket_name,
64 - object_name=f"{file.filename}",
65 - file_path=temp_file_path,
66 - content_type=data.content_type,
67 - )
54 + tmp.write(content)
55 + temp_file_path = tmp.name
56
69 - # Optionally, remove the temporary file after upload
70 - os.remove(temp_file_path)
57 + try:
58 + await client.fput_object(
59 + bucket_name=data.bucket_name,
60 + object_name=f"{file.filename}",
61 + file_path=temp_file_path,
62 + content_type=data.content_type,
63 + )
64 + finally:
65 + if os.path.exists(temp_file_path):
66 + os.remove(temp_file_path)
67
68
69 async def download_data_store(bucket_name: str, object_name: str) -> bytes:
@@ -138,15 +134,13 @@ async def upload_file_to_datastore(
134
135 logger.info(f"Uploading file {file.filename} to bucket {bucket_name} as {object_name}")
136
141 - # Define the temporary file path
142 - temp_file_path = os.path.join(os.getcwd(), file.filename)
143 -
144 - # Save the file to the temporary location and calculate hash
137 + # Save the file to a secure temporary location and calculate hash
138 sha256_hash = hashlib.sha256()
146 - async with aiofiles.open(temp_file_path, "wb") as out_file:
139 + with tempfile.NamedTemporaryFile(delete=False, dir="/tmp") as tmp:
140 content = await file.read()
148 - await out_file.write(content)
141 + tmp.write(content)
142 sha256_hash.update(content)
143 + temp_file_path = tmp.name
144
145 file_hash = sha256_hash.hexdigest()
146 file_size = os.path.getsize(temp_file_path)
@@ -154,16 +148,17 @@ async def upload_file_to_datastore(
148 # Determine content type
149 content_type = file.content_type or "application/octet-stream"
150
157 - # Upload the file to MinIO
158 - await client.fput_object(
159 - bucket_name=bucket_name,
160 - object_name=object_name,
161 - file_path=temp_file_path,
162 - content_type=content_type,
163 - )
164 -
165 - # Remove the temporary file after upload
166 - os.remove(temp_file_path)
151 + try:
152 + # Upload the file to MinIO
153 + await client.fput_object(
154 + bucket_name=bucket_name,
155 + object_name=object_name,
156 + file_path=temp_file_path,
157 + content_type=content_type,
158 + )
159 + finally:
160 + if os.path.exists(temp_file_path):
161 + os.remove(temp_file_path)
162
163 logger.info(f"Successfully uploaded {file.filename} ({file_size} bytes) to {bucket_name}/{object_name}")
164
@@ -215,21 +210,20 @@ async def upload_sysmon_config(customer_code: str, file: UploadFile) -> None:
210
211 logger.info(f"Uploading sysmon config for customer {customer_code}")
212
218 - # Define the temporary file path
219 - temp_file_path = os.path.join(os.getcwd(), file.filename)
220 -
221 - # Save the file to the temporary location
222 - async with aiofiles.open(temp_file_path, "wb") as out_file:
213 + # Save the file to a secure temporary location
214 + with tempfile.NamedTemporaryFile(delete=False, dir="/tmp") as tmp:
215 content = await file.read()
224 - await out_file.write(content)
216 + tmp.write(content)
217 + temp_file_path = tmp.name
218
219 # Upload the file to MinIO with customer folder structure
220 object_name = f"{customer_code}/sysmon_config.xml"
221
229 - await client.fput_object(bucket_name=bucket_name, object_name=object_name, file_path=temp_file_path, content_type="application/xml")
230 -
231 - # Remove the temporary file after upload
232 - os.remove(temp_file_path)
222 + try:
223 + await client.fput_object(bucket_name=bucket_name, object_name=object_name, file_path=temp_file_path, content_type="application/xml")
224 + finally:
225 + if os.path.exists(temp_file_path):
226 + os.remove(temp_file_path)
227
228 logger.info(f"Successfully uploaded sysmon config for customer {customer_code}")
229
@@ -415,8 +409,6 @@ async def store_file_in_minio(
409 Returns:
410 dict: Upload details including success status, object_key, file_size, and file_hash
411 """
418 - import tempfile
419 -
412 client = await create_session()
413
414 # Create bucket if it doesn't exist
@@ -432,20 +424,21 @@ async def store_file_in_minio(
424 file_size = len(file_content)
425
426 # Create a temporary file to upload
435 - with tempfile.NamedTemporaryFile(delete=False) as temp_file:
427 + with tempfile.NamedTemporaryFile(delete=False, dir="/tmp") as temp_file:
428 temp_file.write(file_content)
429 temp_file_path = temp_file.name
430
439 - # Upload the file to MinIO
440 - await client.fput_object(
441 - bucket_name=bucket_name,
442 - object_name=object_key,
443 - file_path=temp_file_path,
444 - content_type=content_type,
445 - )
446 -
447 - # Remove the temporary file after upload
448 - os.remove(temp_file_path)
431 + try:
432 + # Upload the file to MinIO
433 + await client.fput_object(
434 + bucket_name=bucket_name,
435 + object_name=object_key,
436 + file_path=temp_file_path,
437 + content_type=content_type,
438 + )
439 + finally:
440 + if os.path.exists(temp_file_path):
441 + os.remove(temp_file_path)
442
443 logger.info(f"Successfully stored file ({file_size} bytes) to {bucket_name}/{object_key}")
444
backend/app/data_store/data_store_routes.py
+1
@@ -33,6 +33,7 @@ agent_data_store_router = APIRouter()
33 "/upload",
34 response_model=FileUploadResponse,
35 description="Upload a file to the data store",
36 + dependencies=[Depends(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
37 )
38 async def upload_file(
39 file: UploadFile = File(...),