FIX: 11 bugs bảo mật/ổn định (static mount chặn dotfile, delete traversal, cleanup giữ clips serverFileId, upload whitelist, password strength, auth audio endpoints, pedalboard==0.9.19, vendor CDN local) + FEATURE: Carla bridge preview/export MIDI notes âm VSTi (POST /midi-render, /carla-play-notes, nút Preview VSTi/Export MIDI->Audio; pedalboard 0.9.19 raw MIDI bytes; SONICFORGE_STORAGE_DIR cô lập test storage)
This commit is contained in:
+21
-4
@@ -16,6 +16,12 @@ router = APIRouter()
|
||||
|
||||
MAX_AUDIO_UPLOAD_BYTES = 1024 * 1024 * 1024 # 1 GB
|
||||
|
||||
# Bug #4: whitelist extension upload — trước đây nhận mọi đuôi (`.exe`) rồi
|
||||
# serve qua static. Chỉ chấp nhận định dạng audio phổ biến.
|
||||
ALLOWED_AUDIO_EXTENSIONS = {
|
||||
"wav", "mp3", "ogg", "flac", "aiff", "aif", "m4a", "aac", "opus", "webm",
|
||||
}
|
||||
|
||||
def _safe_file_id(file_id: str) -> str:
|
||||
"""Strip any path components from a client-supplied file id."""
|
||||
if not file_id:
|
||||
@@ -81,7 +87,10 @@ async def upload_audio(file: UploadFile = File(...), current_user: Optional[dict
|
||||
if current_user:
|
||||
enforce_password_changed(current_user)
|
||||
user_id = current_user["user_id"] if current_user else "anonymous"
|
||||
ext = os.path.splitext(file.filename or "")[1]
|
||||
ext = os.path.splitext(file.filename or "")[1].lower()
|
||||
# Bug #4: reject non-audio extensions before saving
|
||||
if ext and ext.lstrip(".") not in ALLOWED_AUDIO_EXTENSIONS:
|
||||
raise HTTPException(status_code=400, detail=f"Định dạng file không được hỗ trợ: {ext}")
|
||||
if not ext:
|
||||
ext = ".wav"
|
||||
file_id = f"user_{user_id}_{uuid.uuid4()}{ext}"
|
||||
@@ -125,7 +134,9 @@ async def upload_audio(file: UploadFile = File(...), current_user: Optional[dict
|
||||
}
|
||||
|
||||
@router.post("/edit")
|
||||
async def edit_audio(req: EditRequest):
|
||||
async def edit_audio(req: EditRequest, current_user: Optional[dict] = Depends(get_optional_user)):
|
||||
if current_user:
|
||||
enforce_password_changed(current_user)
|
||||
# Use uploaded file if it exists, or look in processed if it was already edited
|
||||
if not _resolve_storage_path(req.file_id):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
@@ -192,10 +203,12 @@ async def analyze_audio_with_ai(req: AIAnalysisRequest):
|
||||
}
|
||||
|
||||
@router.post("/export")
|
||||
async def export_audio(req: ExportRequest):
|
||||
async def export_audio(req: ExportRequest, current_user: Optional[dict] = Depends(get_optional_user)):
|
||||
"""
|
||||
API endpoint xuất tệp âm thanh sang nhiều định dạng (WAV/MP3/OGG).
|
||||
"""
|
||||
if current_user:
|
||||
enforce_password_changed(current_user)
|
||||
source_path = _resolve_storage_path(req.file_id)
|
||||
if not source_path:
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
@@ -392,7 +405,11 @@ async def list_user_files(req: MyFilesRequest, current_user: dict = Depends(get_
|
||||
async def delete_user_file(file_id: str, current_user: dict = Depends(get_current_user)):
|
||||
user_id = current_user["user_id"]
|
||||
prefix = f"user_{user_id}_"
|
||||
|
||||
# Bug #2: sanitize TRƯỚC khi check prefix — trước đây chỉ check startswith
|
||||
# nên `user_<id>_../../tmp/x` pass guard → os.remove xóa file ngoài storage.
|
||||
file_id = _safe_file_id(file_id)
|
||||
if not file_id:
|
||||
raise HTTPException(status_code=404, detail="Không tìm thấy tệp trên server")
|
||||
# Guard: only own files can be deleted
|
||||
if not file_id.startswith(prefix):
|
||||
raise HTTPException(status_code=403, detail="Bạn không có quyền xóa tệp này")
|
||||
|
||||
Reference in New Issue
Block a user