generated from ttipo/project-base
docs(rules): 11단계 부품 — 공개 링크 보안 · 대용량 업로드 예외 · 스킬 표 · env
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 20m57s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m47s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m52s
CI / Infra (compose · Dockerfile · nginx) (pull_request) Successful in 1m53s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m28s
CI (Flutter) / Flutter (analyze · test) (push) Has been cancelled
CI / Backend (lint · type-check · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test · build) (push) Has been cancelled
CI / Infra (compose · Dockerfile · nginx) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 20m57s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m47s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m52s
CI / Infra (compose · Dockerfile · nginx) (pull_request) Successful in 1m53s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m28s
CI (Flutter) / Flutter (analyze · test) (push) Has been cancelled
CI / Backend (lint · type-check · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test · build) (push) Has been cancelled
CI / Infra (compose · Dockerfile · nginx) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
- security: 대용량 첨부는 확장자 허용 목록만 보는 예외와 그 세 겹, 공개 링크(토큰 해시 · 공개 호스트 분리 · 조건부 UPDATE · 경로마다 전체 상한 · 앞단과 백엔드가 경로를 같게 읽기 — 대소문자 두 겹). - nestjs: 파일 저장 · 조각 올리기 / 공개 링크 · 내려받기 행, 라우팅은 대소문자를 가린다(createHttpAdapter), 경로 숫자 함정, 확장 때 옮길 것(ChunkGate · 검사 차례 · cron · FILE_BLOB_STORE). - vue3: 대용량 첨부 행. docker: 두 server · 스니펫 · 받기 location · limit_conn · 머리글 버퍼 · ClamAV 절 · 이미지 표 · 볼륨 · CI · Git Bash 경로 변환 함정. env-structure · checklist: SHARE_PUBLIC_URL(포털과 다른 호스트 — CORS_ORIGIN 에 넣지 않음) · CLAMAV_HOST. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #7.
This commit is contained in:
@@ -48,6 +48,9 @@
|
||||
|
||||
- **확장자와 MIME 을 둘 다** 검사한다 (`upload.config.ts`). 확장자만 보면 위장이 쉽고,
|
||||
MIME 만 보면 클라이언트가 보낸 값이라 신뢰할 수 없다.
|
||||
- **예외 — 대용량 첨부(11단계 `modules/file-storage`)는 확장자 허용 목록만 본다.** 브라우저가 주는 형식은 dwg · psd · 7z 같은 파일에 믿을 근거가 없어(비거나 제각각)
|
||||
MIME 으로 거르면 정상 파일만 막힌다. 대신 세 겹으로 막는다: 내려줄 때 늘 `application/octet-stream` + `attachment` + `nosniff` + CSP `sandbox`(브라우저가 열지 않는다),
|
||||
허용 목록에 브라우저가 실행하는 형식이 없다, 내용은 ClamAV 가 본다(검사 전 · 중에는 받을 수 없다) — 계약 4절 "정한 것" 10.
|
||||
- **허용 목록(allowlist) 방식**으로만 받는다. 차단 목록은 언제나 빠뜨린다.
|
||||
`.html`·`.svg`·`.js` 등 **브라우저가 실행할 수 있는 형식은 넣지 않는다.**
|
||||
- **저장 파일명은 서버가 생성**한다(UUID + 검증된 확장자). 클라이언트가 보낸 이름을 경로에 쓰면
|
||||
@@ -63,6 +66,19 @@
|
||||
> 이름이 `.png` 이고 MIME 이 `image/png` 이면 내용이 HTML 이어도 통과한다.
|
||||
> 그래서 위의 "정적 서빙 금지"가 중요하다 — 내용 기반 검증이 필요하면
|
||||
> 매직 바이트를 확인하는 라이브러리(`file-type` 등)를 파이프라인에 추가한다.
|
||||
> (서명 그림은 매직 바이트로 판별하고 — `modules/mail-signature/signature-image-format.ts`, 대용량 첨부는 악성 검사(ClamAV)가 내용을 본다.)
|
||||
|
||||
### 공개 링크 (로그인 없이 받는 길 — 11단계 대용량 첨부)
|
||||
|
||||
- 토큰은 서버가 만든 256비트(`randomBytes(32)` base64url)이고 **DB 에는 SHA-256 만** 둔다. 모양이 아니면 DB 에 묻지 않는다.
|
||||
원문 토큰은 로그 · 오류 context · 목록 응답 어디에도 두지 않는다 — 요청 주소는 `maskUrlForLog()`(경로 어디서든 `public-links` 뒤 조각을 가린다), 앞단 nginx 접근 로그도 가린다.
|
||||
- 공개 경로는 **따로 둔 공개 호스트**(`SHARE_PUBLIC_URL` — `files.`)에서만 열고 포털에서는 닫는다(nginx 의 호스트별 server + 백엔드 `public-host.ts` 두 겹).
|
||||
인증 쿠키는 host-only 라 공개 호스트로 가지 않는다. 운영에서 공개 호스트를 `CORS_ORIGIN` 에 넣으면 기동하지 않는다(분리가 꺼지므로).
|
||||
- 받은 수 상한은 한 문장의 조건부 UPDATE(`… WHERE count < limit RETURNING`)로 센다 — 읽고 나서 쓰면 동시 요청이 상한을 넘는다.
|
||||
- 버퍼링을 끈 내려받기는 연결 하나가 앞단 연결 둘과 백엔드 fd 를 잡는다 — **IP 마다 · 전체 동시 연결 상한**(nginx `limit_conn`)을 함께 둔다.
|
||||
전체 상한은 경로(공개 · 앱 안)마다 따로 — 함께 세면 바깥 주소 몇십 개로 직원의 받기까지 막힌다.
|
||||
**앞단과 백엔드가 같은 경로를 같게 읽어야 한다** — 백엔드가 대소문자를 가리지 않던 때 `/FILES/` 처럼 글자 하나로 nginx 의 상한 location 을 비켜 갔다(2026-10-30 리뷰).
|
||||
그래서 nginx 의 받기 location 은 대소문자를 가리지 않는 정규식이고, 백엔드 라우팅은 대소문자를 가린다(두 겹 — [[nestjs]] "전역 HTTP 설정").
|
||||
|
||||
## 네트워크 · 인프라
|
||||
|
||||
|
||||
Reference in New Issue
Block a user