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(...),