fix: security file upload (#3923)

* fix: add check user authentication
* fix: add request body multipart boundary validation
This commit is contained in:
Ítalo Johnny 2024-09-30 16:14:03 -03:00 committed by GitHub
commit f873309004
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 44 additions and 2 deletions

2
.gitignore vendored
View file

@ -273,3 +273,5 @@ src/frontend/temp
*-shm
*-wal
.history
.dspy_cache/

View file

@ -40,6 +40,8 @@ def get_flow_id(
async def upload_file(
file: UploadFile,
flow_id: UUID = Depends(get_flow_id),
current_user=Depends(get_current_active_user),
session=Depends(get_session),
storage_service: StorageService = Depends(get_storage_service),
):
try:
@ -50,6 +52,10 @@ async def upload_file(
)
flow_id_str = str(flow_id)
flow = session.get(Flow, flow_id_str)
if flow.user_id != current_user.id:
raise HTTPException(status_code=403, detail="You don't have access to this flow")
file_content = await file.read()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
file_name = file.filename or hashlib.sha256(file_content).hexdigest()

View file

@ -1,6 +1,7 @@
import asyncio
import json
import os
import re
import warnings
from contextlib import asynccontextmanager
from http import HTTPStatus
@ -8,7 +9,7 @@ from pathlib import Path
from urllib.parse import urlencode
import nest_asyncio # type: ignore
from fastapi import FastAPI, HTTPException, Request, Response
from fastapi import FastAPI, HTTPException, Request, Response, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
@ -127,6 +128,39 @@ def create_app():
)
app.add_middleware(JavaScriptMIMETypeMiddleware)
@app.middleware("http")
async def check_boundary(request: Request, call_next):
if "/api/v1/files/upload" in request.url.path:
content_type = request.headers.get("Content-Type")
if not content_type or "multipart/form-data" not in content_type or "boundary=" not in content_type:
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={"detail": "Content-Type header must be 'multipart/form-data' with a boundary parameter."},
)
boundary = content_type.split("boundary=")[-1].strip()
if not re.match(r"^[\w\-]{1,70}$", boundary):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={"detail": "Invalid boundary format"},
)
body = await request.body()
boundary_start = f"--{boundary}".encode()
boundary_end = f"--{boundary}--\r\n".encode()
if not body.startswith(boundary_start) or not body.endswith(boundary_end):
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={"detail": "Invalid multipart formatting"},
)
response = await call_next(request)
return response
@app.middleware("http")
async def flatten_query_string_lists(request: Request, call_next):
flattened: list[tuple[str, str]] = []

View file

@ -123,7 +123,7 @@ class CustomComponent(Component):
];
for (const text of allVisibleTexts) {
await expect(page.getByText(text)).toBeVisible();
await expect(page.getByText(text).last()).toBeVisible();
}
await page.locator(".ag-cell-value").first().click();