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:
2026-08-10 18:46:54 +07:00
parent 67ad2b8e4b
commit 61cb4b846f
33 changed files with 1097 additions and 103 deletions
+21 -4
View File
@@ -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")