Files
llm-wiki/raw/branch-notes/feature-file-resource-handling-contract.md
T

261 lines
24 KiB
Markdown

---
title: branch / feature-file-resource-handling-contract
source_type: branch-note
status: raw
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-023
kind: project-work-item
project: ca-skeleton-operational-contract
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-023
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1]
refines: []
overrides: []
depends_on: []
contract_packet: 1
branch: feature-file-resource-handling-contract
parent_branch:
related_projects: [ca-skeleton]
tags: [branch, ca-skeleton, file, resource]
created: 2026-05-22
target_merge:
status_label: in-progress
contract_packet_sha256: 7db5c6a4eb77af61706b5f4688cf721f786789638595dca733d334c5dca3d3c0
---
# branch: feature-file-resource-handling-contract
> Layer: `raw/branch-notes/` — file/resource 처리 실패 기준을 정의합니다.
<!-- section-id: branch-parent -->
## 부모 (필수)
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: file size·type·storage boundary test가 통과한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1` | Gradle multi-module에서 domain-core·application-core·adapter-*·shared-contract·app-bootstrap·sample-portfolio 책임을 분리한다 | file/resource 처리의 application·adapter 책임 경계에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
<!-- GENERATED: branch-contract:end -->
## 묶음
<!-- GENERATED: sources:start -->
- [[raw/company-tech-blogs/file-clamav-icap-gateway-scan]]
- [[raw/official-docs/file-s3-presigned-url-upload]]
- [[raw/official-docs/file-tus-resumable-upload-protocol]]
- [[raw/official-docs/iana-media-types-registry]]
- [[raw/official-docs/jdk-files-createtempfile]]
- [[raw/official-docs/nginx-client-max-body-size]]
- [[raw/official-docs/owasp-file-upload-cheat-sheet]]
- [[raw/official-docs/owasp-path-traversal]]
- [[raw/official-docs/spring-boot-multipart-reference]]
- [[raw/official-docs/spring-mvc-async-streaming]]
- [[raw/official-docs/spring-streaming-response-body]]
<!-- GENERATED: sources:end -->
> 본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
### 오류 기록 (본 feature 작업 중 발생)
- (없음 — 현재 documented-only 단계)
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (없음 — Phase C2 실 구현 단계에 누적)
<!-- section-id: branch-goal -->
## 목표
multipart 실패만으로는 파일 처리 기준이 부족합니다. upload size, temp file cleanup, streaming failure, content type sniffing, path traversal 방지를 skeleton 기준에 포함해야 합니다.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- upload size limit.
- multipart parse failure.
- temp file cleanup.
- download streaming failure.
- content type sniffing 금지.
- path traversal 방지.
- resource exhaustion 분류.
### 제외 범위
- 실제 object storage adapter 구현.
- antivirus scan 구현.
- CDN/download product policy.
## TODO
> TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Decisionized Work Items" / "테스트 계약" 참조. upload size/multipart parse/temp cleanup/streaming/content-type allowlist/path traversal/resource exhaustion/antivirus 위치 모두 결정 라인 또는 표 row로 반영됨. 잔존 TODO 없음.
## Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 `기준 작성`으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
| --- | --- | --- |
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | `wiki/projects` 승급 위치 |
## 진행 중 메모
- file/resource handling은 API contract와 runtime lifecycle 양쪽에 걸칩니다.
## 결정 사항 (decisions)
- 2026-05-22: file/resource handling을 별도 운영 표면으로 분리.
- 2026-05-22: antivirus/file scanning은 기본 off. 활성화 위치는 gateway, async worker, app inline 중 하나로 명시해야 하며 미정이면 업로드 feature 승급 불가.
- 2026-05-22: upload size limit 기본값은 10MB, file sample은 core v1에 포함하지 않음.
- 2026-05-22: size limit enforcement layer SSOT = Spring `spring.servlet.multipart.max-file-size` 10MB + global request size 12MB. gateway/WAF는 보조(20MB hard limit). Spring 단의 enforcement가 실패 시 envelope 응답 보장.
- 2026-05-22: 3계층 분리는 의도된 defense-in-depth: gateway 20MB는 raw 413 직격 차단 (envelope 우회), global 12MB는 multipart 외 raw body 한도, Spring 10MB는 multipart 단일 file 한도. 모든 한도 위반은 Spring 단에서 분류되어 envelope 응답으로 변환.
- 2026-05-22: temp file cleanup trigger = (1) success/failure on close (try-with-resources), (2) startup sweeper for orphaned files older than 1h, (3) JVM shutdown hook은 backup. file >1h not closed → orphan.
- 2026-05-22: allowed content-type allowlist starting set = image/jpeg, image/png, image/webp, application/pdf, text/csv, application/zip. 추가는 endpoint별 registry 등록.
- 2026-05-22: streaming download backpressure = response timeout 60s, max stream 100MB. 초과 시 truncate + ERROR log.
- 2026-05-22: file storage abstraction은 outbound = object store call이 EXTERNAL_OUTBOUND_ALLOWED capability 요구.
- 2026-05-22: antivirus default = scan position = "gateway" (외부 upload-가능 endpoint), in-app 검증은 disabled. 활성화 시 별도 worker로 분리.
## 결정-근거 매핑
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. Company-tech-blog / vendor blog 인용은 사례 (`company-case-study`) 로만 사용, 공식 best practice 단정 금지.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | file/resource handling 을 별도 운영 표면으로 분리 | UNSUPPORTED_DECISION — 내부 조직/스코프 결정 | `internal-only` | 다른 branch (lifecycle/outbound) 와 책임 경계 lint 필요 |
| D2 | antivirus/file scanning default = off, 활성화 위치는 gateway/worker/app 중 1개 명시 강제 | `raw/company-tech-blogs/file-clamav-icap-gateway-scan.md#CLAMAV-ICAP-C1`, `raw/company-tech-blogs/file-clamav-icap-gateway-scan.md#CLAMAV-ICAP-C2` (ICAP = `official-standard` strength) | `official-standard + official-vendor-doc` | gateway 위치 default 권고는 `company-case-study` 영역 — 공식 best practice 단정 금지 |
| D3 | upload size limit default = 10MB, file sample core v1 미포함 | **메커니즘 SUPPORTED**: `raw/official-docs/spring-boot-multipart-reference.md#SB-MULTIPART-C1` (Servlet 5 `Part` API 채택, `official-vendor-doc`), `raw/official-docs/spring-boot-multipart-reference.md#SB-MULTIPART-C2` (Spring Boot default per-file 1MB / per-request 10MB, `official-vendor-doc`). **정량값 UNSUPPORTED_DECISION**: per-file 10MB default 는 Spring Boot upstream default (1MB) 와 다르며 ca-tmpl 자체 결정 — 외부 권고 부재 | `official-vendor-doc (mechanism only)` | 10MB 정량값은 ca-tmpl 자체 결정 — endpoint registry override 정책으로 보완 필요. 'file sample core v1 미포함' 은 internal scope 결정 (UNSUPPORTED) |
| D4 | size limit enforcement SSOT = Spring (multipart 10MB + global request 12MB), gateway/WAF 는 보조 (20MB hard) | **메커니즘 SUPPORTED**: `raw/official-docs/spring-boot-multipart-reference.md#SB-MULTIPART-C3` (`MultipartProperties``spring.servlet.multipart` prefix 로 max size / 저장 위치 / disk flush threshold override 가능, `official-vendor-doc`), `raw/official-docs/spring-boot-multipart-reference.md#SB-MULTIPART-C4` (`max-file-size=-1` 로 unlimited 설정 가능, `official-vendor-doc`), `raw/official-docs/nginx-client-max-body-size.md#NGINX-CMB-C1` (`client_max_body_size size;` syntax, `official-vendor-doc`), `raw/official-docs/nginx-client-max-body-size.md#NGINX-CMB-C4` (초과 시 HTTP 413 응답, `official-vendor-doc`). **정량값 UNSUPPORTED_DECISION**: 12MB global / 20MB gateway 정량값은 ca-tmpl 자체 결정 — 외부 권고 부재 | `official-vendor-doc (mechanism only)` | 12MB / 20MB 정량값은 ca-tmpl 추론. nginx default 는 1MB (`NGINX-CMB-C2`) 임을 명시 — 20MB 는 의도적 override |
| D5 | 3계층 분리 (gateway 20MB / global 12MB / Spring 10MB) 는 defense-in-depth | **SUPPORTED**: `raw/official-docs/owasp-file-upload-cheat-sheet.md#OWASP-FUP-C1` (extension allowlist 만으로는 불충분 → 다층 검증 필요, `official-reference`), `raw/official-docs/owasp-file-upload-cheat-sheet.md#OWASP-FUP-C2` (Content-Type 헤더 신뢰 불가 → server-side 검증 별도 필요, `official-reference`), `raw/official-docs/owasp-file-upload-cheat-sheet.md#OWASP-FUP-C3` (UUID/GUID 랜덤 파일명 essential, `official-reference`) | `official-reference` | OWASP cheatsheet 는 reference (표준 아님). 3계층 size 분리 자체는 size 검증의 defense-in-depth — OWASP 가 직접 '3-layer size limit' 권고하는 raw 인용은 없음, 다층 검증 원칙 일반화 |
| D6 | temp file cleanup 3 trigger: try-with-resources + startup sweeper (>1h orphan) + JVM shutdown hook (backup) | **메커니즘 SUPPORTED**: `raw/official-docs/jdk-files-createtempfile.md#JDK-TEMPFILE-C6` (`DELETE_ON_CLOSE` 옵션으로 close 시 자동 삭제, `official-vendor-doc`), `raw/official-docs/jdk-files-createtempfile.md#JDK-TEMPFILE-C7` (shutdown-hook 또는 `File.deleteOnExit()` 로 자동 삭제 가능, `official-vendor-doc`). **정량값 UNSUPPORTED_DECISION**: 1h orphan threshold 는 ca-tmpl 자체 결정 — JDK doc 은 threshold 정량값 권고 없음 | `official-vendor-doc (mechanism only)` | 1h orphan threshold 외부 권고 부재. `JDK-TEMPFILE-C7``deleteOnExit()` 는 SIGKILL 등 abnormal termination 보장 없음 — startup sweeper 가 그 gap 메움 |
| D7 | content-type allowlist starting set = image/jpeg, image/png, image/webp, application/pdf, text/csv, application/zip | **SUPPORTED**: `raw/official-docs/iana-media-types-registry.md#IANA-MEDIA-C1` (Media Types 의 assignment/listing 은 IANA 단일 registry, `official-standard`), `raw/official-docs/iana-media-types-registry.md#IANA-MEDIA-C5` (top-level types: `application`, `image`, `text`, ... — allowlist 6종이 모두 IANA top-level 내, `official-standard`), `raw/official-docs/owasp-file-upload-cheat-sheet.md#OWASP-FUP-C5` (webroot 밖 저장 + administrative access only, `official-reference`) | `official-standard + official-reference` | 6종 starting set 선정 자체는 ca-tmpl 도메인 결정 — IANA 는 registry 권위만, endpoint 별 권고 없음. 추가 endpoint registry 등록 정책으로 보완 |
| D8 | streaming download = response timeout 60s + max stream 100MB, 초과 시 truncate + ERROR log | **메커니즘 SUPPORTED**: `raw/official-docs/spring-streaming-response-body.md#SPRING-STREAM-RB-C3` (`StreamingResponseBody` 의 명시된 use case = file download, `official-vendor-doc`), `raw/official-docs/spring-streaming-response-body.md#SPRING-STREAM-RB-C4` (`ResponseEntity` body 로 사용 가능 — status/header 커스터마이즈, `official-vendor-doc`). **정량값 UNSUPPORTED_DECISION**: 60s timeout / 100MB max stream / truncate 정책 모두 ca-tmpl 자체 결정 — Spring doc 은 정량값 권고 없음 | `official-vendor-doc (mechanism only)` | timeout 60s 는 Spring default 의존 (`SPRING-STREAM-RB-C8` — 컨테이너 의존) 과 다른 명시값. truncate 동작 자체는 Spring 가 보장하지 않음 — 자체 구현 필요 |
| D9 | file storage abstraction = outbound, object store call 은 EXTERNAL_OUTBOUND_ALLOWED capability 요구 | UNSUPPORTED_DECISION — 내부 capability 모델 결정 | `internal-only` | capability 모델의 lint 필요 |
| Path-traversal claim | filename 입력 검증 = normalized storage key only + opaque key (raw path passthrough 금지) — Decisionized Work Items 의 `path traversal` row 근거 | **SUPPORTED**: `raw/official-docs/owasp-path-traversal.md#OWASP-PT-C1` (path traversal = web root 밖 파일/디렉토리 접근 공격 정의, `official-reference`), `raw/official-docs/owasp-path-traversal.md#OWASP-PT-C2` (공격 벡터: `../` sequence + variation + absolute path, `official-reference`), `raw/official-docs/owasp-path-traversal.md#OWASP-PT-C3` (방어 원칙: "known good only" allowlist, sanitize 금지, `official-reference`) | `official-reference` | OWASP community wiki 는 reference (표준 아님). URL encoding (`OWASP-PT-C4`) / null byte (`OWASP-PT-C5`) variation 도 별도 검증 필요 — opaque key 정책이 모든 variation 차단 가정은 별도 contract test 필요 |
| D10 | antivirus default scan position = gateway, in-app disabled, 활성화 시 별도 worker 분리 | `raw/company-tech-blogs/file-clamav-icap-gateway-scan.md#CLAMAV-ICAP-C2` (ICAP `official-standard`), `raw/company-tech-blogs/file-clamav-icap-gateway-scan.md#CLAMAV-ICAP-C3` (ICAP virus scan use case, `official-standard`), `raw/company-tech-blogs/file-clamav-icap-gateway-scan.md#CLAMAV-ICAP-C1` (ClamAV daemon model, `official-vendor-doc`) | `official-standard + official-vendor-doc` | ICAP 가 HTTPS E2E TLS 환경에서 적용 어려움 (raw 메모에 명시) — 본 결정의 제약. gateway 가 default 라는 정량 권고는 ca-tmpl 자체 추론 |
| D11 | 대안 1 (Direct S3 presigned URL upload) — app via 3-layer 우회 가능하나 antivirus 위치 분리 필요 | `raw/official-docs/file-s3-presigned-url-upload.md#FS3-PRE-C1`, `raw/official-docs/file-s3-presigned-url-upload.md#FS3-PRE-C2`, `raw/official-docs/file-s3-presigned-url-upload.md#FS3-PRE-C4`, `raw/official-docs/file-s3-presigned-url-upload.md#FS3-PRE-C5` | `official-vendor-doc` | post-upload async scan + quarantine bucket 패턴 별도 설계 필요 (raw 메모 참조) |
| D12 | 대안 2 (tus.io resumable upload) — 100MB+ 영상 적합하나 "1h orphan cleanup" 충돌 위험 | `raw/official-docs/file-tus-resumable-upload-protocol.md#TUS-RUP-C1`, `raw/official-docs/file-tus-resumable-upload-protocol.md#TUS-RUP-C3`, `raw/official-docs/file-tus-resumable-upload-protocol.md#TUS-RUP-C4`, `raw/official-docs/file-tus-resumable-upload-protocol.md#TUS-RUP-C5` | `official-standard` | tus session timeout 과 orphan threshold 분리 필요 — 채택 시 D6 의 1h threshold 수정 |
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| Spring 10MB + global 12MB + gateway 20MB 3계층이 ca-tmpl 트래픽 프로파일에 적합 | 정량값 외부 권고 부재 (D3/D4/D5) | k6 부하테스트로 413 응답 비율 + Spring multipart parser 동작 확인 | `planned` |
| Temp file cleanup 3 trigger 가 모두 정확히 동작 (1h orphan 정확 식별) | JDK 공식 doc 인용 부재 (D6) | `TempFileCleanupContractTest` 로 정상/예외/timeout 3 경로 cleanup 확인 + startup sweeper orphan(>1h) 삭제 verify | `planned` |
| Antivirus gateway 위치가 ca-tmpl 의 HTTPS termination 정책과 호환 | ICAP 가 HTTPS E2E TLS 환경에서 적용 어려움 (raw 메모) | gateway HTTPS termination 정책 확인 + ICAP server 통합 PoC | `needs-confirmation` |
| ClamAV signature DB 갱신 주기 + 운영 책임 주체 (gateway team vs app team) | raw 에 명시 없음 (Usage Boundary 참조) | 운영 협약 문서 작성 + signature update cron 확인 | `needs-confirmation` |
| Direct S3 대안 채택 시 EXTERNAL_OUTBOUND_ALLOWED capability 매핑 | raw `FS3-PRE-*` 는 S3 메커니즘만 보장, ca-tmpl 자체 capability 모델과의 매핑은 별도 | capability 모델 contract test + signing 호출 경로 추적 | `planned` |
| tus 채택 시 session timeout 과 orphan threshold 가 정상 case 를 삭제하지 않음 | `TUS-RUP-C5` 는 max-size 만 정의, session lifetime 침묵 | tus session 정책 + ca-tmpl orphan threshold 분리 contract test | `planned` |
| Content-type allowlist 6개 starting set 이 ca-tmpl 도메인 endpoint 별로 충분 | IANA registry 또는 endpoint 별 권고 부재 (D7) | endpoint별 use case 인터뷰 + allowlist 누락 endpoint inventory | `needs-confirmation` |
| Streaming download 100MB / 60s timeout 이 적합 | 정량값 외부 권고 부재 (D8) | 실제 파일 크기 분포 측정 + truncate 발생률 확인 | `planned` |
| Path traversal opaque key 정책이 모든 upload 경로 (legacy 포함) 적용 | raw 인용 부재 — OWASP 또는 Spring Security 공식 doc 권고 | `traversal test` + storage layer code review | `planned` |
## Decisionized Work Items
| item | Decision | Allowed | Forbidden | Required test |
| --- | --- | --- | --- | --- |
| upload size | 10MB default | endpoint override with registry | unlimited upload | oversized upload test |
| scanning | off by default, owner required if enabled | gateway/worker/app inline | "somewhere scans it" assumption | scan owner checklist |
| path traversal | normalized storage key only | object-store opaque key | raw path passthrough | traversal test |
| temp cleanup | bounded temp dir + cleanup on failure | streaming direct to storage | orphan temp files | cleanup test |
## 구현 가이드
현재 `documented-only` 단계이며 구현 위치·클래스·메커니즘 anchor는 아직 고정되지 않았다. 구현 결정은 기존 `## Decision Evidence Map / 결정-근거 매핑`의 D-row를 변경하지 않고 후속 구현 단계에서 연결한다.
## 엣지·실패·의존
- **실패·엣지 경로**: 아래 `## 테스트 계약`의 oversized upload, temp cleanup, path traversal, streaming failure, antivirus 위치 검사를 따른다.
- **다른 계약 의존**: API contract와 runtime lifecycle 양쪽 경계를 소비하며, object store 호출 capability는 D9가 정의한 outbound 경계를 따른다.
## 테스트 계약
- oversized upload가 generic 500으로 처리되면 실패.
- temp cleanup contract: 다음 3 trigger가 모두 구현되어야 함: (a) success/failure on close (try-with-resources), (b) startup sweeper for orphaned files > 1h, (c) JVM shutdown hook backup. 측정 방법: contract test `TempFileCleanupContractTest`에서 `File.createTempFile` 후 정상/예외/timeout 3 경로 각각의 cleanup 확인. orphan(>1h not closed) file이 startup sweeper에 의해 삭제되는지 verify.
- path traversal input이 storage path로 전달되면 실패.
- download stream failure가 traceId 없이 로그되면 실패.
- antivirus 위치 명시 강제: `APP_FILE_UPLOAD_ENABLED=true`이면 결정 사항에 antivirus 위치(`gateway` 또는 `worker` 또는 `off` 중 1개)가 명시되어 있어야 함. 측정 방법: branch note의 결정 사항 라인에서 `antivirus.position` token grep. 미명시 시 readiness fail. default는 `gateway` 권고.
## 근거 (필수, 최소 1개+)
> 본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| [[raw/company-tech-blogs/file-clamav-icap-gateway-scan]] | ClamAV + RFC 3507 ICAP (ca-tmpl antivirus gateway default의 표준 근거 |
| [[raw/official-docs/file-s3-presigned-url-upload]] | app/gateway 부담 0 vs antivirus 위치 분리 필요 + quarantine bucket |
| [[raw/official-docs/file-tus-resumable-upload-protocol]] | 100MB+ 영상에 적합 vs ca-tmpl "1h orphan cleanup"이 session 잘못 삭제 가능 |
| [[raw/official-docs/spring-boot-multipart-reference]] | D3 (upload size 메커니즘) + D4 (Spring multipart enforcement SSOT 메커니즘) — `official-vendor-doc` |
| [[raw/official-docs/nginx-client-max-body-size]] | D4 (gateway/WAF 보조 layer 메커니즘 — `client_max_body_size` directive) — `official-vendor-doc` |
| [[raw/official-docs/owasp-file-upload-cheat-sheet]] | D5 (3-layer defense-in-depth: extension/Content-Type/저장 위치 다층 검증) + D7 (webroot 밖 저장) — `official-reference` |
| [[raw/official-docs/jdk-files-createtempfile]] | D6 (temp file cleanup 메커니즘: `DELETE_ON_CLOSE` + shutdown-hook + `deleteOnExit`) — `official-vendor-doc` |
| [[raw/official-docs/iana-media-types-registry]] | D7 (content-type allowlist 6종이 IANA top-level types 내) — `official-standard` |
| [[raw/official-docs/spring-streaming-response-body]] | D8 (streaming download 메커니즘: `StreamingResponseBody` + `ResponseEntity`) — `official-vendor-doc` |
| [[raw/official-docs/owasp-path-traversal]] | Path-traversal claim (공격 정의 + 벡터 + "known good only" allowlist 방어 원칙) — `official-reference` |
## 외부 근거 / 대안 조사 (2026-05-22 — Group G-J: File / Resource Handling)
본 branch의 Spring 10MB + global 12MB + gateway 20MB 3-layer + content-type allowlist 6종 + temp orphan 1h cleanup + antivirus gateway default + path traversal opaque key 결정에 대한 외부 source.
- **채택 결정 (app via 3-layer + gateway antivirus + ClamAV/ICAP)**:
- [[raw/company-tech-blogs/file-clamav-icap-gateway-scan]] — ClamAV + RFC 3507 ICAP (ca-tmpl antivirus gateway default의 표준 근거)
- **검토한 대안**:
- **대안 1: Direct S3 presigned URL upload (app via 우회)** — [[raw/official-docs/file-s3-presigned-url-upload]] (app/gateway 부담 0 vs antivirus 위치 분리 필요 + quarantine bucket)
- **대안 2: tus resumable upload protocol** — [[raw/official-docs/file-tus-resumable-upload-protocol]] (100MB+ 영상에 적합 vs ca-tmpl "1h orphan cleanup"이 session 잘못 삭제 가능)
- **대안 3: in-app ClamAV daemon scan** — `file-clamav-icap-gateway-scan` 동일 source 안에서 in-app/gateway/async 3종 비교 (in-app은 app instance에 daemon dependency)
- **대안 4: Post-upload async scan (S3 + Lambda ClamAV)** — 동일 source (app/gateway 부담 0 vs scan 완료 전 객체 존재 → quarantine bucket 분리 필요)
- **비교 핵심**: ca-tmpl "gateway default" 선택은 app instance scaling과 무관한 일정 throughput + in-app daemon dependency 회피. HTTPS E2E TLS 환경에서는 ICAP 적용 어려움 — 그 경우 post-upload async가 대안. tus 채택 시 ca-tmpl "1h orphan cleanup"은 session 정상 case도 삭제할 위험 — session ↔ orphan threshold 분리 보강 필요.
## 마주친 문제
- 아직 없음.
## 관련 일일 노트
- 없음.
## 완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목:
- `locally-verified` 항목:
- `prod-verified` 항목:
- **추출하지 않을 항목** (planned / documented-only / abandoned):