fix: add security validations to duplication upload API
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled

P0-1: Add MIME type whitelist validation (video files only)
- Accept: mp4, mpeg, mov, avi, webm, mkv, 3gp
- Reject non-video files with 415 Unsupported Media Type

P0-2: Add file size limit validation
- Use OSS_DIRECT_UPLOAD_MAX_MB config (default 800MB)
- Check Content-Length header before reading file
- Verify actual file size after reading
- Return 413 Request Entity Too Large if exceeded

Reference: apps/api/app/api/routes/upload.py implementation
This commit is contained in:
API文档维护Agent
2026-06-28 15:58:07 +08:00
parent 312dad7497
commit ea0bde3742
2 changed files with 56 additions and 6 deletions
@@ -1,7 +1,7 @@
"""Phase 2 - 查重功能:duplication_records + duplication_segments
Revision ID: 011
Revises: 010
Revision ID: 012
Revises: 011
Create Date: 2026-06-28
This migration creates two new tables:
@@ -12,8 +12,8 @@ from alembic import op
import sqlalchemy as sa
# revision identifiers
revision = "011"
down_revision = "010"
revision = "012"
down_revision = "011"
branch_labels = None
depends_on = None
+52 -2
View File
@@ -30,6 +30,32 @@ logger = logging.getLogger(__name__)
router = APIRouter()
# 查重功能只接受视频文件
ALLOWED_VIDEO_MIME_TYPES = frozenset({
"video/mp4", "video/mpeg", "video/quicktime", "video/x-msvideo",
"video/webm", "video/x-matroska", "video/3gpp",
})
def _validate_video_mime_type(content_type: str | None) -> str:
"""验证视频文件的 MIME 类型,如果无效则抛出异常。"""
if not content_type:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Content-Type header is required",
)
# 处理带参数的类型,如 "video/mp4; charset=utf-8"
base_type = content_type.split(";")[0].strip().lower()
if base_type not in ALLOWED_VIDEO_MIME_TYPES:
raise HTTPException(
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
detail=f"只支持视频文件。支持的类型: mp4, mpeg, mov, avi, webm, mkv, 3gp",
)
return base_type
def _to_record_response(record: DuplicationRecord) -> DuplicationRecordResponse:
return DuplicationRecordResponse(
@@ -86,6 +112,21 @@ async def upload_for_duplication(
detail="文件名不能为空",
)
# P0-1: 验证 MIME 类型(只接受视频文件)
validated_content_type = _validate_video_mime_type(file.content_type)
# P0-2: 验证文件大小(参考 OSS_DIRECT_UPLOAD_MAX_MB
from app.config import get_settings
settings = get_settings()
max_size_bytes = settings.OSS_DIRECT_UPLOAD_MAX_MB * 1024 * 1024
# 先检查 Content-Length header(如果可用)
if file.size is not None and file.size > max_size_bytes:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail=f"文件超过上传限制 ({settings.OSS_DIRECT_UPLOAD_MAX_MB}MB)",
)
# 读取文件内容并上传到 OSS
file_id = uuid4().hex[:8]
safe_filename = file.filename.replace("/", "_").replace("\\", "_")
@@ -94,6 +135,15 @@ async def upload_for_duplication(
try:
content = await file.read()
file_size = len(content)
# 再次检查实际文件大小
if file_size > max_size_bytes:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail=f"文件超过上传限制 ({settings.OSS_DIRECT_UPLOAD_MAX_MB}MB)",
)
except HTTPException:
raise
except Exception as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -104,7 +154,7 @@ async def upload_for_duplication(
storage_service.upload_file(
content,
storage_key,
content_type=file.content_type or "video/mp4",
content_type=validated_content_type,
)
except Exception as exc:
raise HTTPException(
@@ -176,7 +226,7 @@ def delete_duplication_record(
duplication_repository: Any = Depends(get_duplication_repository),
) -> None:
"""删除查重记录。"""
# 检查记录是否存在且属于当前用户
# 检查记录是否存在且属于当前用户
detail_uc = GetDuplicationDetailUseCase(duplication_repository)
record = detail_uc.execute(record_id)
if record is None or record.user_id != authenticated_user.user.id: