업로드 보안과 외부 스캐너
사용자 업로드 기능을 운영에 노출할 때 확인해야 할 스토리지와 악성코드 검사 기준입니다.
기본 원칙
| 항목 | 권장 |
|---|---|
| 저장소 | Cloudflare R2 같은 S3-compatible object storage |
| 로컬 디스크 | local/testing 용도만 |
| private 파일 | 직접 URL 금지, presigned URL 사용 |
| public 파일 | 직접 공유, 썸네일, 샘플, SEO/OG 이미지 등 공개 가능 자산만 |
| scanner | 기본은 off, 운영 업로드는 ClamAV 선택 설치 권장 |
업로드 자산을 서버 로컬 디스크에 묶지 않으면 배포, 백업, 확장, 프로젝트 이식이 단순해집니다.
Public / Private 결정 기준
업로드 기능을 구현하기 전 비즈니스 모델에 따라 public/private 공개 정책을 먼저 결정합니다. 판단이 애매하면 AI 는 구현 전에 사용자에게 확인해야 하며, 기본값은 private 입니다.
| 질문 | 권장 visibility | 이유 |
|---|---|---|
| 이미지 URL 자체가 외부에 공유되어야 하는가? | public | presigned URL 은 만료되므로 직접 이미지 공유에 부적합 |
| SEO/OG/랜딩/브랜드 이미지인가? | public | 검색엔진, 메신저 미리보기, 캐시가 안정 URL 을 필요로 함 |
| 로그인/권한/결제/고객사별 접근 제어가 필요한가? | private | URL 이 노출되어도 영구 접근되면 안 됨 |
| 원본 업로드, 내부 검토 자료, 사용자별 파일인가? | private | 기본 보안 원칙상 비공개가 안전 |
| 페이지 공유만 필요하고 이미지 단독 공유는 필요 없는가? | private 가능 | 페이지 요청 시 애플리케이션이 presigned URL 을 재발급할 수 있음 |
접근 방식 3가지
| 방식 | 보안 의미 | 적합한 사례 | 주의 |
|---|---|---|---|
| public domain URL | object key 를 알면 누구나 접근 가능 | scripture.how 색상 의미 이미지, 공개 콘텐츠, SEO/OG 이미지 | URL 이 한 번 공유되면 만료 없이 접근 가능 |
| public bucket + 랜덤 key | listing 은 안 되므로 URL 을 모르면 찾기 어려움 | 민감하지 않지만 외부 노출돼도 큰 문제가 없는 콘텐츠 자산 | 권한 보안이 아니라 경로 은닉임. 로그, referer, 공유로 URL 이 새면 계속 접근 가능 |
| private bucket + temporary URL | 권한 확인 후 만료 URL 로 접근 | 사용자 파일, 고객사 자료, 결제/학습/개인정보 관련 자료 | 이미지 URL 자체를 장기 공유해야 하는 용도에는 부적합 |
scripture.how 처럼 콘텐츠 자체가 공개 학습 자료이고 직접 이미지 공유가 필요한 서비스는 public bucket 1개로 운영할 수 있습니다. 반대로 사용자 업로드 원본, 고객사별 비공개 자료, 권한 기반 자료가 생기면 public bucket 에 섞지 말고 private bucket 또는 별도 접근 제어를 사용합니다.
public 과 private 은 가능하면 bucket 또는 disk 를 분리합니다. 단일 bucket 을 쓰는 MVP 라도 key prefix, visibility, read/write credential 을 분리해야 합니다.
R2 설정
CONTENT_ASSET_* 값은 프로젝트 루트 .env 에 설정합니다.
CONTENT_ASSET_PRIVATE_DISK=r2-private-rw
CONTENT_ASSET_PRIVATE_READ_DISK=r2-private
CONTENT_ASSET_PUBLIC_DISK=r2-public
CONTENT_ASSET_KEY_PREFIX=assets
CONTENT_ASSET_R2_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
CONTENT_ASSET_R2_REGION=auto
CONTENT_ASSET_R2_PRIVATE_BUCKET=
CONTENT_ASSET_R2_PUBLIC_BUCKET=
CONTENT_ASSET_R2_PUBLIC_URL=
CONTENT_ASSET_R2_READ_ACCESS_KEY_ID=
CONTENT_ASSET_R2_READ_SECRET_ACCESS_KEY=
CONTENT_ASSET_R2_WRITE_ACCESS_KEY_ID=
CONTENT_ASSET_R2_WRITE_SECRET_ACCESS_KEY=
read token 과 write token 을 분리하는 것을 권장합니다. 학습자/공개 조회 경로는 read disk 만 사용하고, 관리자/AI 업로드 경로만 write disk 를 사용합니다.
AssetLibrary 기본 검사
AssetLibrary 는 외부 scanner 없이도 다음 검사를 수행합니다.
- 확장자 whitelist/blocklist
- MIME 타입 검증
- 파일 signature/magic byte 검증
- 파일 크기 제한
- 원본 파일명 미사용
- HTML/JS/SVG/실행 파일 기본 차단
SVG 는 기본 차단합니다. SVG를 허용해야 하는 경우 sanitizer 가 있는 별도 경로를 사용하세요.
ClamAV 선택 설치
ClamAV 는 리눅스/서버에서 널리 쓰이는 GPLv2 malware scanning engine 입니다. multi-saas-kit 기본 배포본에는 포함하지 않고, 필요한 프로젝트에서 별도 컨테이너 또는 서비스로 설치합니다.
ASSET_LIBRARY_SCANNER=none # none | auto | clamav
ASSET_LIBRARY_CLAMAV_HOST=clamav
ASSET_LIBRARY_CLAMAV_PORT=3310
ASSET_LIBRARY_CLAMAV_TIMEOUT=10
ASSET_LIBRARY_CLAMAV_FAIL_OPEN=false
| 모드 | 동작 |
|---|---|
none | ClamAV 를 사용하지 않음 |
auto | clamd 연결 가능 시 사용, 없으면 malware scan skip |
clamav | clamd 연결 실패 또는 감염 탐지 시 업로드 차단 |
ClamAV 는 애플리케이션 코드 변경 없이 clamd 서비스와 env 설정만 맞추면 AssetLibrary adapter 로 연결됩니다.
기본 배포는 가볍게 유지하고, 업로드 보안이 필요한 프로젝트만 scanner 를 추가할 수 있습니다.
외부 모듈 라이선스 원칙
GPL/AGPL/상용/라이선스 불명확 모듈은 multi-saas-kit 기본 배포본에 포함하지 않습니다. 이런 모듈은 선택 설치형 adapter, 별도 컨테이너, 별도 서비스로 분리합니다.
설치 전에는 다음을 확인합니다.
- 라이선스와 상업 배포 가능성
- 전이 의존성 라이선스
- 외부 통신 여부
- 권한 상승 시도 여부
- 시크릿 수집 여부
배포 체크리스트
- production/staging 업로드 자산은 R2/S3-compatible object storage 사용
- private/public bucket 또는 prefix 분리
- 비즈니스 모델 기준으로 public/private 공개 정책 결정
- 이미지 URL 자체 공유가 필요하면 public/CDN/custom domain 사용
- read/write token 분리
-
.env에만 bucket/credential 설정 -
ASSET_LIBRARY_SCANNER정책 결정 - 사용자 업로드 운영 노출 시 ClamAV 선택 설치 검토
- GPL/AGPL/상용 모듈은 기본 배포에 포함하지 않음