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

24 KiB

title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
title source_type status id kind project work_item inherits refines overrides depends_on contract_packet branch parent_branch related_projects tags created target_merge status_label contract_packet_sha256
branch / feature-file-resource-handling-contract branch-note raw BR-CA-SKELETON-OPERATIONAL-CONTRACT-023 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-023
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1
1 feature-file-resource-handling-contract
ca-skeleton
branch
ca-skeleton
file
resource
2026-05-22 in-progress 7db5c6a4eb77af61706b5f4688cf721f786789638595dca733d334c5dca3d3c0

branch: feature-file-resource-handling-contract

Layer: raw/branch-notes/ — file/resource 처리 실패 기준을 정의합니다.

부모 (필수)

ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: file size·type·storage boundary test가 통과한다

상속한 프로젝트 결정

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

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

묶음

본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

오류 기록 (본 feature 작업 중 발생)

  • (없음 — 현재 documented-only 단계)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • (없음 — Phase C2 실 구현 단계에 누적)

목표

multipart 실패만으로는 파일 처리 기준이 부족합니다. upload size, temp file cleanup, streaming failure, content type sniffing, path traversal 방지를 skeleton 기준에 포함해야 합니다.

  • 이슈:
  • PR:

범위

포함 범위

  • 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 (MultipartPropertiesspring.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-C7deleteOnExit() 는 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):
  • 검토한 대안:
    • 대안 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 protocolraw/official-docs/file-tus-resumable-upload-protocol (100MB+ 영상에 적합 vs ca-tmpl "1h orphan cleanup"이 session 잘못 삭제 가능)
    • 대안 3: in-app ClamAV daemon scanfile-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):