Docker
이미지 업로드 API에 파일 크기 제한을 두기 전에 정한 다섯 가지
용량 숫자만 검사해서는 부족했던 이미지 업로드 API의 검증 항목을 체크리스트로 정리했습니다.
10MB 제한만으로는 안심할 수 없었다
프로필 이미지 업로드에 10MB 이하 조건을 넣고 끝냈는데, 테스트 중 확장자가 .jpg인 텍스트 파일도 통과했다. 브라우저가 보낸 Content-Type도 신뢰할 수 없었다. 업로드는 저장소 비용 문제이면서 동시에 입력 검증 문제다.
아래 기준은 사용자가 올린 파일을 웹에서 다시 제공하는 작은 서비스에 적용했다.
업로드 경로 체크리스트
- 요청 본문의 최대 크기를 프록시와 애플리케이션 양쪽에서 제한한다.
- 파일명 확장자가 아니라 파일 내용의 MIME 타입을 검사한다.
- 허용한 형식만 디코딩해 실제 이미지인지 확인한다.
- 원본 파일명으로 저장하지 않고 서버가 만든 식별자를 쓴다.
- 공개 URL과 업로드 권한을 분리하고, 필요하면 바이러스 검사 대기 상태를 둔다.
Nginx의 제한은 큰 요청이 앱까지 도달하는 일을 줄인다.
server {
client_max_body_size 10m;
}
다만 이것만으로 API 규칙이 되지는 않는다. 애플리케이션도 파일이 없는 경우, 개수 초과, 허용하지 않은 타입을 각각 400 또는 413으로 처리해야 클라이언트가 원인을 알 수 있다.
타입은 선언값과 실제 내용을 같이 본다
Node 환경에서는 업로드 미들웨어가 제공하는 mimetype을 첫 필터로 쓸 수 있지만 최종 판정으로 삼지 않았다. 이미지 라이브러리로 메타데이터를 읽어 디코딩 실패를 거르고, 허용 목록을 좁혔다.
const allowed = new Set(['image/jpeg', 'image/png', 'image/webp'])
if (!file || !allowed.has(file.mimetype)) {
return Response.json({ error: 'unsupported image type' }, { status: 400 })
}
const metadata = await sharp(file.buffer).metadata()
if (!metadata.width || !metadata.height) {
return Response.json({ error: 'invalid image' }, { status: 400 })
}
실제 구현에서는 픽셀 수 제한도 추가했다. 파일 크기는 작아도 지나치게 큰 해상도는 메모리를 많이 쓸 수 있기 때문이다. SVG는 스크립트와 외부 참조 처리 규칙이 달라 처음에는 허용 목록에서 뺐다.
배포 뒤에 확인할 일
- 0바이트 파일, 제한보다 큰 파일, 위장 확장자 파일을 각각 올린다.
- 업로드 실패 메시지가 내부 저장소 경로를 노출하지 않는지 본다.
- 저장된 파일의 URL이 원본 파일명을 포함하지 않는지 확인한다.
- 성공한 파일도 이미지 변환·삭제 정책에 맞게 정리되는지 본다.
제한 값 하나를 고르는 것보다, 서버가 받은 바이트를 어디까지 믿을지 정하는 일이 업로드 API의 핵심이었다.