From 13888af2c29ac6544db9260e43a14fa21df96363 Mon Sep 17 00:00:00 2001 From: ttipo Date: Tue, 27 Oct 2026 07:49:41 +0900 Subject: [PATCH] =?UTF-8?q?docs(rules):=20=EC=84=9C=EC=8B=9D=20=EB=B3=B8?= =?UTF-8?q?=EB=AC=B8=20=EC=A0=95=ED=99=94=C2=B7=EC=A0=95=ED=99=94=20?= =?UTF-8?q?=EC=A0=84=20=EB=B9=84=EC=9A=A9=20=EA=B2=80=EC=82=AC=C2=B7?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20env=20=ED=91=9C=EA=B8=B0=20=EA=B7=9C?= =?UTF-8?q?=EC=B9=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - security: 사용자가 쓴 HTML 은 서버가 허용 목록으로 다시 정화한다, 밖에서 온 HTML 은 정화 전에 비용부터 잰다, 브라우저에서 외부 HTML 을 고칠 때는 떨어진 문서 안에서 끝낸다 - env-structure: 숫자 env 는 앞자리 0 없는 10진수, nginx 도 읽는 크기 값은 6자리 이하 - nestjs·vue3·docker 스킬: 골격 표(정화·사전 검사·편집기·값 규칙 짝 파일)와 체크리스트, 발송 경로 4MB, 컨테이너의 npm install 이 lock 을 깨뜨린 사례(2026-10-27) Co-Authored-By: Claude Opus 5.5 --- .claude/rules/env-structure.md | 5 +++++ .claude/rules/security.md | 6 ++++++ .claude/skills/docker/SKILL.md | 9 ++++++++- .claude/skills/nestjs/SKILL.md | 10 ++++++++-- .claude/skills/vue3/SKILL.md | 9 ++++++++- 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.claude/rules/env-structure.md b/.claude/rules/env-structure.md index 957fbdc..32ffd9b 100644 --- a/.claude/rules/env-structure.md +++ b/.claude/rules/env-structure.md @@ -37,6 +37,11 @@ description: 환경변수 단일 진실 공급원(SSOT) 구조 — 루트 .env.{ - **compose 는 정의되지 않은 변수를 빈 문자열로 주입한다.** 코드에서 `??` 기본값만 쓰면 `Number('')===0` 같은 함정에 빠진다 — 숫자 설정값은 `shared/utils/env.ts` 의 `parsePositiveInt()` 처럼 빈 문자열까지 방어하는 파서를 거친다. (기존 `.env` 를 쓰는 사람이 새 키를 추가하지 않아도 안전해야 한다) +- **숫자 값은 앞자리 0 없는 10진수로만 쓴다** — `08`·`+5`·`5.0`·`1e3`·`0x10` 은 env 검증이 기동을 막는다 + (기간 `015m`, `TRUST_PROXY` 홉 수·CIDR 접두도 같다. 규칙은 `shared/utils/env.ts` 의 `isPositiveIntString()`). + 같은 값을 읽는 nginx 템플릿(`frontend/nginx-env.envsh`)이 셸 산술 때문에 `08` 을 거부하므로 백엔드도 같은 규칙을 쓴다 (2026-10-27). + nginx 본문 한도가 되는 `MAIL_ATTACHMENT_MAX_SIZE_MB`·`UPLOAD_MAX_SIZE_MB` 는 템플릿과 같이 **6자리(999999) 이하**만 받는다 + (백엔드만 뜨고 frontend 가 뜨지 않는 조합을 막는다 — `env.validation.ts` 의 `NGINX_SHARED_INT_KEYS`). - `docker-compose.yml` 의 `env_file` 지시자 때문에 `backend/.env.{APP_ENV}` 파일은 비어 있어도 **존재해야 한다.** - **프론트엔드 dev 서버는 compose 를 거치지 않는다** — 호스트에서 직접 띄우므로([[docker]]) `vite.config.ts` 가 루트 `.env.{mode}` 를 `loadEnv` 로 읽어 포트·프록시 대상을 정한다. diff --git a/.claude/rules/security.md b/.claude/rules/security.md index 800a8b3..5999b91 100644 --- a/.claude/rules/security.md +++ b/.claude/rules/security.md @@ -33,6 +33,12 @@ - **서버 서명 키(`JWT_SECRET` 등)나 거기서 파생한 키를 쓰지 않는다** — 평문을 아는 받은 사람이 (평문, HMAC) 쌍으로 서명 키 후보를 오프라인에서 대입해 볼 수 있고, 맞히면 토큰을 위조할 수 있다 (2026-10-26 리뷰) - 에러 응답에 스택·쿼리·내부 호스트명을 싣지 않는다 — 미정의 예외는 항상 `SYS_001` 로 수렴시킨다 ([[error-codes]]) +- 사용자가 쓴 HTML(메일 서식 본문)은 **서버가 허용 목록으로 다시 정화**한 뒤 내보낸다 — 클라이언트 편집기의 출력을 믿지 않는다. + style 은 속성마다 값 형식을 처음부터 끝까지(`^…$`) 맞춘다 (`modules/mail/html/sanitize-outgoing-html.ts`, 계약 2절 "서식 본문") +- 받은 메일·서식 본문처럼 **밖에서 온 HTML 은 정화하기 전에 비용부터 잰다** — 정화기(htmlparser2)는 중첩 깊이의 제곱으로 느려져 + 메일 한 통으로 서버 전체를 수 분 멈출 수 있다 (2026-10-27 리뷰). 넘으면 정화하지 않는다 (`modules/mail/html/html-parse-budget.ts`, [[nestjs]]) +- 브라우저에서 외부 HTML 을 고쳐 쓸 때는 떨어진 문서(`