v1.10.88 품질·보안 개선과 미디어 카탈로그 database 전환을 반영한다.

세션·로그인 제한, ZIP/업로드 스트리밍, CSP 최소 강제와 Report-Only, Playwright 격리 검증을 추가하고 미디어 목록을 reconcile 후 database 모드로 고정한다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-22 11:05:40 +09:00
parent 9bc0f0a90e
commit de767095ba
116 changed files with 8128 additions and 2933 deletions

View File

@@ -1,5 +1,89 @@
# 업데이트 이력
## v1.10.88
- Playwright API 통합 테스트 10건과 Chromium E2E 테스트 3건 추가.
- Playwright 실행마다 `_test` 전용 PostgreSQL 컨테이너·볼륨과 임시 업로드 루트를 생성하고 종료 시 제거하도록 격리.
- 테스트 중 게시물 Export worker와 추천 사이트 파비콘 cron을 비활성화하고 Playwright worker를 1개로 고정.
- `test:unit`, `test:api`, `test:e2e`, `verify:ci` 검증 명령 추가 및 로컬 `verify`와 CI 전체 검증 범위 분리.
- CSP를 `base-uri 'self'`, `object-src 'none'`, `frame-ancestors 'self'` 강제 최소 정책과 사용자 코드·광고·외부 미디어 호환 관찰용 Report-Only 정책으로 분리.
- 전체 HTML 문서를 반환하는 `/pages/**`에는 강제 CSP를 적용하지 않고 Report-Only 수집만 유지.
- `/api/csp-report`에 32KB 본문 제한, JSON 계열 Content-Type 검증, 최대 20건 payload 검증, IP 해시 기준 분당 60회 제한 추가.
- CSP 보고에서 문서 URL의 쿼리·fragment와 차단 URL의 경로·쿼리를 제거하고 브라우저 계열·지시어·origin 단위로만 DB 집계.
- `068_csp_violation_aggregates.sql` 마이그레이션으로 CSP 위반 집계 테이블·최근 관찰 인덱스 추가.
- `069_media_catalog.sql` 마이그레이션으로 `media_metadata` 카탈로그 필드·상태·관계, `media_references`, `media_catalog_state` 추가.
- 미디어 목록 조회를 `filesystem` 기본, `shadow` 비교, `database` 전환 모드로 분리하고 준비 미완료·집계 불일치·DB 조회 실패 시 파일시스템 결과로 대체.
- DB 모드에서 탭·폴더·종류·미사용·파일명/사용처 검색·50/100/200개 페이지네이션을 SQL로 처리.
- 업로드·Import·썸네일·아바타·이름 변경·삭제와 게시물·페이지·태그·회원·설정·네비게이션 저장에 미디어 파일·참조 카탈로그 최선 노력 dual-write 적용.
- 파일을 삭제하거나 이동하지 않는 안전 reconcile로 발견 파일 upsert, 콘텐츠 참조 백필, 사라진 파일 `missing` 표시, 파일시스템/DB 개수·용량 준비 상태 집계 추가.
- 관리자 `POST /admin/api/media/reconcile`과 로컬 전용 `npm run media:reconcile:dev` 수동 재동기화 경로 추가.
- 관리자 미디어 화면에 임시 전환 도구(장부 정리·비교 모드·DB 모드) 추가. 전환 완료 후 삭제 예정.
- 임시 전환 도구에 기존 방식(filesystem) 버튼을 추가해 shadow·database와 화면 비교가 가능하게 함.
- 개발 검증 완료 후 임시 전환 UI·`catalog-mode`/`catalog-status` API·런타임 모드 덮어쓰기를 제거.
- 개발(`.env.development`)과 예시(`.env.example`)의 `MEDIA_CATALOG_MODE`를 `database`로 고정. 운영도 reconcile 후 동일하게 고정.
- 기존 방식(filesystem) 사용처 계산에 네비게이션 추천 사이트 파비콘(`cachedFaviconUrl`)을 포함해 DB 모드와 맞춤.
- 사이트 설정 미디어 참조 백필 필드명을 camelCase로 정리해 사용처 라벨이 `favicon_url` 원문으로 나오지 않게 수정.
- 기존 방식이 페이지 대표 이미지를 카드 썸네일 상태로만 판정해 `원본` 배지가 빠지던 문제를 수정.
- DB 모드에서 프로필 `avatarOwner`와 `user.avatarUrl` 참조를 중복 합산하던 사용 개수 배지를 기존 방식과 같게 맞춤.
- 프로필에 연결된 미디어 상세에서도 게시물 본문 등 다른 사용처를 함께 표시.
- multipart 업로드에서 busboy가 UTF-8 한글 파일명을 Latin-1로 오해하던 문제를 복구해 macOS NFD 한글 파일명도 NFC로 저장.
- 개발 DB reconcile에서 파일 124개·77,019,754 bytes와 DB 카탈로그의 개수·용량 차이 0 확인.
- DB 모드에서 레거시 `-body.webp`만 있는 원본을 `hasThumbnail`로 오판해 없는 `body-720`을 그리드에 넣어 이미지가 깨지던 문제 수정.
- 카드·본문 파생 존재 판별을 `card`/`body-1600`/`body-720` URL 기준으로 파일시스템 모드와 맞추고, 그리드 배지를 썸네일 프레임 안으로 고정.
- 파생 미리보기 실패 시 Vue 재렌더에도 원본 URL이 유지되도록 폴백 상태를 항목 URL 기준으로 보관.
- 공유형 요청 제한은 현재 단일 서버 운영 방침에 따라 이번 후속 범위에서 제외.
## v1.10.87
- 관리자 사이트 설정을 카드별 부분 저장 API로 변경해 다른 카드의 미저장 입력 덮어쓰기 방지.
- 초안 이탈 직전 자동 저장 실패·진행 중·저장 불가능 상태에서 화면 이동 차단.
- 페이지 작성·수정 화면에 미저장 변경 이탈 확인 추가.
- 저장 요청 중 추가된 페이지 입력을 저장 완료로 오판하지 않도록 서버 전송 payload 기준점 적용.
- 새 페이지 생성 후 이탈이 보류되어도 다음 저장은 생성된 페이지 수정으로 이어지도록 보완.
- 게시물 `:::html` 블록의 정규식 필터를 허용 태그·속성 기반 HTML parser sanitizer로 교체.
- HTML 블록의 이벤트 속성·실행 URL·script/iframe/SVG/form 계열 요소와 인라인 style 차단.
- 외부 새 창 링크에 `noopener noreferrer` 자동 적용 및 `sanitize-html` 의존성 추가.
- 일반 관리자 다중 업로드를 전체 Buffer 적재에서 임시 디스크 스트리밍 방식으로 변경.
- multipart 수신 중 파일 수·전체 용량·종류별 개별 용량을 제한하고 실패 시 임시·이동 파일 정리.
- 미디어 전체 파일·사용처 스캔에 30초 공유 캐시와 동시 요청 단일 실행 적용.
- 업로드·삭제·이름/폴더 변경·썸네일 재생성 시 미디어 캐시 즉시 무효화.
- Export worker를 서버 시작·30초 주기로 복구하고 PostgreSQL advisory lock으로 다중 인스턴스 중복 실행 차단.
- `croner` 직접 의존성 추가 및 운영 의존성 취약점 0건 재확인.
- 존재하지 않는 `/tag/:slug` 요청이 빈 목록 200 대신 실제 404를 반환하도록 수정.
- 같은 댓글 컴포넌트에서 게시물 slug가 바뀔 때 목록·답글·입력·오류·좋아요 진행 상태 초기화 및 재조회.
- 이전 게시물의 지연된 댓글 응답이 새 게시물 상태를 덮어쓰지 않도록 요청 식별 처리.
- 멤버십 본문 접근 권한과 댓글 조회·작성·좋아요 접근 정책 통일.
- 왼쪽·오른쪽 고정 사이드바의 top·높이를 `--site-top-chrome-height` 기준으로 변경해 어나운스 바 겹침 수정.
- Docker PostgreSQL 호스트 포트를 기본 `127.0.0.1`에만 바인딩하고 `DB_BIND_ADDRESS` 환경 변수 추가.
- 개별 마이그레이션 SQL 실행과 `schema_migrations` 이력 기록을 `psql --single-transaction`으로 원자화.
- Node 내장 테스트 러너 기반 HTML sanitizer·multipart 스트리밍 회귀 테스트 4건 추가.
- `npm run verify`를 문법 검사·회귀 테스트·프로덕션 빌드 순서로 보강.
- 전역 `nosniff`·Referrer Policy·SAMEORIGIN·Permissions Policy와 CSP Report-Only 보안 헤더 추가.
- 공개 통계 pageview·heartbeat에 IP 해시 기준 분당 쓰기 요청 상한 추가.
## v1.10.86
- 게시물 Export ZIP을 전체 Buffer 생성 방식에서 디스크 스트리밍 방식으로 변경.
- Export 자산은 이미지·영상·첨부 파일 원본 바이트를 재압축하지 않고 ZIP store 방식으로 보존.
- 게시물 Import multipart ZIP을 메모리 적재 대신 임시 파일 스트리밍 저장으로 변경.
- Import ZIP 엔트리 수·개별/전체 압축 해제 크기·압축률·Markdown 총량 제한 추가.
- Import 자산을 엔트리별 스트림으로 저장하고 처리 완료 후 임시 ZIP 자동 삭제.
- ZIP 스트리밍용 `archiver`, `unzipper`, `busboy` 직접 의존성 추가.
## v1.10.85
- Nuxt 3.21.9와 전이 의존성 보안 패치 적용 및 운영 의존성 취약점 0건 확인.
- 회원·관리자 세션에 DB 세션 버전 검증을 추가하고 비밀번호 변경 시 기존 세션 무효화.
- 회원·관리자 로그인에 계정·IP 기준 15분 실패 횟수 제한 추가.
- 공개 게시물 목록 API에 기본 표시 개수 상한을 강제하고 응답에서 전체 본문 제외.
- 세션 버전용 `067_users_session_version.sql` 마이그레이션 추가.
- Nuxt 3.21.9 CLI 옵션 해석에 맞춰 로컬 preview host·port 인자를 등호 형식으로 수정.
## v1.10.84
- 블로그 게시용 업데이트 기록 `v1.1`, `v1.2`, `v1.5`, `v1.6`, `v1.10` 초안 작성.
- 각 공지는 직전 개발 구간의 주요 변화를 일반 독자가 이해하기 쉬운 설명으로 정리.
## v1.10.83
- 관리자 대시보드 AdSense 잔고 카드에 지급 계정 통화 기준 안내 추가.