docs(rules): 3단계 공용 부품·웹 DELETE 금지·로그 예외
CI (Flutter) / Flutter (analyze · test) (pull_request) Has been cancelled
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Has been cancelled
CI / Frontend (lint · type-check · test · build) (pull_request) Has been cancelled
CI / Infra (compose · Dockerfile · nginx) (pull_request) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Has been cancelled

vue3 스킬에 메일 스토어 구성 · 새 메일 확인 · 메일 검색 · 여러 통 처리 행과 새 밀도 예외, nestjs 스킬에 검색 · 영구 삭제 부품,
docker 스킬에 로그 · Referrer-Policy · 외부 프록시의 쿼리 없는 로그, 로그 규칙에 cursor 와 nginx 오류 로그 예외를 더했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 23:56:10 +09:00
parent 5b6a97e5e1
commit 32576b0ad1
4 changed files with 24 additions and 10 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ description: NestJS 백엔드(backend/) 개발 규칙 — 컨트롤러/서비스
| **인증 가드** | `src/common/guards/jwt-auth.guard.ts` | `JwtAuthGuard` — 액세스 토큰 쿠키 검증, 실패는 `AUTH_001`. 전략은 `modules/auth/jwt.strategy.ts` |
| 현재 사용자 데코레이터 | `src/common/decorators/current-user.decorator.ts` | `@CurrentUser()` → `AuthenticatedUser { email, name }` |
| 직원 인증 | `modules/auth` | 쿠키 JWT 로그인·`me`·`refresh`·`logout` (계약 `docs/mail/api-contract.md` 1절). 설정 `auth.settings.ts`, 쿠키 `auth-cookies.ts`, 로그인 실패 횟수 `login-attempt.service.ts`, **로그인 세션(DB)** `auth-session.entity.ts`·`auth-session.policy.ts`(1회용 회전·재사용 감지)·`auth-session.cleanup.ts`(끝난 세션 정리 cron). **메일 엔진 관리자 차단** — 정적 목록(`employee-address.ts` + `MAIL_BLOCKED_ACCOUNTS`)과 로그인·리프레시 때 엔진 권한 확인(`jmap/jmap.client.ts`) 두 겹 |
| 메일 (웹메일 API) | `modules/mail` | 엔진 포트 `mail.provider.ts`(`MAIL_PROVIDER`) + Stalwart JMAP 어댑터 `jmap/`, 받은 메일 HTML 정화 `html/sanitize-mail-html.ts`, 보내는 서식 본문 정화(허용 목록) `html/sanitize-outgoing-html.ts`, 두 정화 앞의 사전 검사(길이·파서 스택 비용, 선형 시간) `html/html-parse-budget.ts`, 첨부 형식 `attachment.policy.ts`, 설정 `mail.settings.ts`, 발송 중복 방지(`Idempotency-Key`) `send-idempotency.ts`, 첨부 임시 디스크 저장 `mail-upload-storage.ts`, 임시 저장 `draft.controller.ts`(엔진 초안 — 새로 만들고 다시 읽은 뒤 옛것을 지운다), 서명 그림 주소 규칙 `html/signature-images.ts`(보낼 때 `cid:` 로), 본문 그림 판정 `html/inline-images.ts`, 받는 사람 추천 `recipient-suggestions.service.ts`(사용자별 10분 캐시) · 순서 `recipient-ranking.ts` · 커서 `recipient-suggestion-cursor.ts` (계약 2절) |
| 메일 (웹메일 API) | `modules/mail` | 엔진 포트 `mail.provider.ts`(`MAIL_PROVIDER`) + Stalwart JMAP 어댑터 `jmap/`, 받은 메일 HTML 정화 `html/sanitize-mail-html.ts`, 보내는 서식 본문 정화(허용 목록) `html/sanitize-outgoing-html.ts`, 두 정화 앞의 사전 검사(길이·파서 스택 비용, 선형 시간) `html/html-parse-budget.ts`, 첨부 형식 `attachment.policy.ts`, 설정 `mail.settings.ts`, 발송 중복 방지(`Idempotency-Key`) `send-idempotency.ts`, 첨부 임시 디스크 저장 `mail-upload-storage.ts`, 임시 저장 `draft.controller.ts`(엔진 초안 — 새로 만들고 다시 읽은 뒤 옛것을 지운다), 서명 그림 주소 규칙 `html/signature-images.ts`(보낼 때 `cid:` 로), 본문 그림 판정 `html/inline-images.ts`, 받는 사람 추천 `recipient-suggestions.service.ts`(사용자별 10분 캐시) · 순서 `recipient-ranking.ts` · 커서 `recipient-suggestion-cursor.ts`, 검색어 → 엔진 조건 `jmap/search-terms.ts`(한국어 조사 표는 여기 한 곳 — 낱말 안 OR · 낱말끼리 AND, 따옴표 구절), 여러 통 `PATCH /api/messages`(엔진 요청 한 번) · 휴지통에서 영구 삭제 `message-deletion.controller.ts`(휴지통의 메일만, 조건부 지우기) — 본문 `dto/bulk-messages.dto.ts`, 새 메일 확인 `GET /api/mail-state`(`mailbox.controller.ts`) (계약 2절) |
| 메일 서명 | `modules/mail-signature` | 사용자마다 서명 하나(DB) + 서명 그림. 그림 형식은 내용으로 판별(매직 바이트·머리의 가로·세로, 새 의존성 없음) `signature-image-format.ts`, 파일은 업로드 볼륨의 `mail-signatures/` (`signature-image-storage.ts`), 서명에 붙지 않은 그림 정리 cron `mail-signature.cleanup.ts`. 메일 모듈이 발송·임시 저장 때 `MailSignatureService` 로 그림을 쓴다 (계약 2절 "서명") |
| 사내 목록 | `modules/mail-directory` | 웹메일 로그인에 성공한 직원(관리자·게이트웨이·막은 계정 제외)을 `AuthService` 가 기록 → 받는 사람 추천의 "사내". 엔진의 계정 목록(Principal) 권한은 쓰지 않는다(일부러 막아 두었다) |
| Rate Limiting | `app.module.ts` 의 `ClientIpThrottlerGuard`(APP_GUARD, `common/guards/client-ip-throttler.guard.ts`) | 초과 시 429 → `REQ_001`. 루트 `.env` 의 `THROTTLE_*` 로 조정. `ThrottlerGuard` 를 상속해 tracker 만 바꿨다 — IP 는 `shared/utils/client-ip.ts` 의 `clientIpKey()`(IPv6 는 /64 로 묶음, 로그인 실패 카운터와 같은 함수) |
@@ -42,7 +42,7 @@ description: NestJS 백엔드(backend/) 개발 규칙 — 컨트롤러/서비스
| 파일 업로드 | `modules/upload` | **로그인 필요**(`JwtAuthGuard`). 로컬 디스크 저장 + 형식·크기 검증 + 안전한 파일명 생성 ([[security]]). 한글 파일명은 multer `defParamCharset: 'utf8'` |
| 헬스체크 | `modules/health` | `@SkipThrottle`+`@SkipTransform` — 기계가 읽는 엔드포인트 패턴 ([[response-format]]) |
| 요청 컨텍스트 | `shared/logger/request-context.ts` | 요청마다 추적 ID 를 AsyncLocalStorage 에 둔다 — 필터를 거치지 않는 로그도 `traceTag()` 로 `[traceId]` 를 붙인다 ([[logging]]) |
| 순수 유틸 | `shared/utils/` | 순수 함수 분리 위치 (예: `mask.ts` 로그용 이메일 마스킹·URL 의 검색어 가리기(`maskUrlForLog` — `q` 는 길이만), `settle.ts` 정해진 시간까지만 기다리기, `duration.ts` 기간 파싱, `content-disposition.ts` 다운로드 헤더, `cookies.ts`, `trust-proxy.ts`, `request-origin.ts`) |
| 순수 유틸 | `shared/utils/` | 순수 함수 분리 위치 (예: `mask.ts` 로그용 이메일 마스킹·URL 의 검색어 가리기(`maskUrlForLog` — `q` 와 `cursor`(검색어 해시를 담는다)는 길이만), `settle.ts` 정해진 시간까지만 기다리기, `duration.ts` 기간 파싱, `content-disposition.ts` 다운로드 헤더, `cookies.ts`, `trust-proxy.ts`, `request-origin.ts`) |
> **확장 시 따르는 규칙** (기능이 필요해지면 규칙대로 구현):
> - **보호 라우트**: 인증은 구현되어 있다 (`modules/auth` + `common/guards/jwt-auth.guard.ts`). 새 보호 라우트는 컨트롤러·핸들러에