본문으로 건너뛰기

업로드 보안과 외부 스캐너

사용자 업로드 기능을 운영에 노출할 때 확인해야 할 스토리지와 악성코드 검사 기준입니다.

기본 원칙

항목권장
저장소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 자체가 외부에 공유되어야 하는가?publicpresigned URL 은 만료되므로 직접 이미지 공유에 부적합
SEO/OG/랜딩/브랜드 이미지인가?public검색엔진, 메신저 미리보기, 캐시가 안정 URL 을 필요로 함
로그인/권한/결제/고객사별 접근 제어가 필요한가?privateURL 이 노출되어도 영구 접근되면 안 됨
원본 업로드, 내부 검토 자료, 사용자별 파일인가?private기본 보안 원칙상 비공개가 안전
페이지 공유만 필요하고 이미지 단독 공유는 필요 없는가?private 가능페이지 요청 시 애플리케이션이 presigned URL 을 재발급할 수 있음

접근 방식 3가지

방식보안 의미적합한 사례주의
public domain URLobject key 를 알면 누구나 접근 가능scripture.how 색상 의미 이미지, 공개 콘텐츠, SEO/OG 이미지URL 이 한 번 공유되면 만료 없이 접근 가능
public bucket + 랜덤 keylisting 은 안 되므로 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
모드동작
noneClamAV 를 사용하지 않음
autoclamd 연결 가능 시 사용, 없으면 malware scan skip
clamavclamd 연결 실패 또는 감염 탐지 시 업로드 차단
설치 후 바로 연계

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/상용 모듈은 기본 배포에 포함하지 않음

관련 문서