From 750cd6362ade9bdc99ac7db9f3092dae0fd7de50 Mon Sep 17 00:00:00 2001 From: ttipo Date: Thu, 17 Sep 2026 21:11:39 +0900 Subject: [PATCH] =?UTF-8?q?fix(backend):=20multer=20=EC=B7=A8=EC=95=BD?= =?UTF-8?q?=EC=A0=90=20=ED=95=B4=EC=86=8C=20=E2=80=94=20overrides=20?= =?UTF-8?q?=EB=A1=9C=202.4.0=20=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @nestjs/platform-express@11 이 multer 를 2.2.0 으로 정확 고정하는데, 이 버전에는 깊게 중첩된 필드명으로 자원을 소모시키는 DoS 취약점(high)이 있다. 상위 패키지가 버전을 박아두어 npm audit fix 로는 해결되지 않았다. platform-express@12 가 쓰는 multer 버전을 확인하니 2.4.0 이었다. 즉 v12 로 올려서 얻는 결과가 multer 상향이므로, overrides 로 하위 패키지만 올려 Nest 생태계(v11) 업그레이드 없이 동일한 결과를 얻는다. - npm audit: 10건 → 0건 - 같은 메이저(2.x) 안에서의 상향이라 API 호환 위험이 낮고, lint·단위 31개·e2e 7개·빌드·런타임 기동·Docker 빌드까지 확인했다. - 이 패턴(전이 의존성 취약점을 overrides 로 끊기)과 주의사항을 security.md 에 규칙으로 정리. platform-express 를 v12 로 올릴 때 override 를 제거해야 한다는 조건도 함께 기록. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/rules/security.md | 30 +++++++++++++++++++++++++----- .claude/skills/nestjs/SKILL.md | 2 ++ backend/package-lock.json | 29 ++++------------------------- backend/package.json | 3 +++ 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/.claude/rules/security.md b/.claude/rules/security.md index aaea7e6..b7b4a48 100644 --- a/.claude/rules/security.md +++ b/.claude/rules/security.md @@ -68,10 +68,30 @@ 2. **수정이 다른 것을 깨뜨리는가** — 생태계 버전이 어긋나면 더 큰 위험 3. 미룬다면 **언제 다시 볼지** 조건을 적어둔다 -> **현재 유예 중인 항목** -> - `multer` (high, DoS via deeply nested field names) — `@nestjs/platform-express` 의 전이 의존성. -> 수정하려면 `@nestjs/platform-express@12` 가 필요한데 나머지 Nest 생태계가 v11 이라 호환이 깨진다. -> **이 템플릿에는 파일 업로드 엔드포인트가 없어 도달 경로가 없다**(`FileInterceptor` 미사용). -> → **파일 업로드를 구현하는 시점에 반드시 재검토**한다. Nest v12 로 일괄 업그레이드하는 것이 정석. +> **현재 유예 중인 항목**: 없음 (`npm audit` 0건) + +### 전이 의존성 취약점은 overrides 로 끊는다 + +직접 의존성이 취약한 하위 패키지를 **정확 버전으로 고정**해 두면 `npm audit fix` 로는 해결되지 않는다. +상위 패키지의 메이저 업그레이드를 기다리지 말고 `package.json` 의 `overrides` 로 하위 패키지만 올린다. + +```jsonc +// backend/package.json +"overrides": { + "multer": "^2.4.0" +} +``` + +> 실제 사례: `@nestjs/platform-express@11` 은 `multer` 를 `2.2.0` 으로 고정하는데 이 버전에 DoS 취약점(high)이 있었다. +> `@nestjs/platform-express@12` 는 `multer@2.4.0` 을 쓰지만, v12 로 올리면 나머지 Nest 생태계(v11)와 호환이 깨진다. +> **v12 가 하는 일이 결국 multer 를 올리는 것**이므로, override 로 같은 결과를 생태계 업그레이드 없이 얻었다. + +규칙: + +- override 는 **상위 패키지가 그 버전을 실제로 쓰는지 근거를 확인**하고 적용한다 + (위 사례는 platform-express v12 가 2.4.0 을 선언하는 것이 근거). +- 같은 메이저 안에서만 올린다. 메이저를 건너뛰면 API 가 달라져 상위 패키지가 깨질 수 있다. +- 적용 후 **lint·테스트·빌드·런타임 기동까지 확인**한다. override 는 npm 이 검증해 주지 않는다. +- 상위 패키지를 정식으로 올릴 때 **override 를 제거**한다. 남겨두면 나중에 버전이 거꾸로 고정된다. > 환경변수 관리 규칙은 [[env-structure]], 도메인별 적용은 [[nestjs]] · [[flutter]] 참조. diff --git a/.claude/skills/nestjs/SKILL.md b/.claude/skills/nestjs/SKILL.md index 4e08c82..b133095 100644 --- a/.claude/skills/nestjs/SKILL.md +++ b/.claude/skills/nestjs/SKILL.md @@ -296,6 +296,8 @@ dc exec backend npm uninstall <패키지> - 변경 후 호스트의 `package.json`·`package-lock.json` 이 갱신됐는지 확인하고 **함께 커밋**한다 (bind mount 라 호스트에 반영된다). - 호스트에서 실행해버렸다면 `git checkout backend/package-lock.json` 으로 되돌린 뒤 위 방법으로 다시 한다. - 커밋 전 `docker compose ... build backend` 로 `npm ci` 가 통과하는지 확인한다. CI 도 같은 검사를 한다. +- 전이 의존성의 취약점이 `npm audit fix` 로 안 풀리면 `package.json` 의 `overrides` 를 쓴다 ([[security]]). + 현재 `multer: ^2.4.0` 이 걸려 있다 — `@nestjs/platform-express` 를 v12 로 올릴 때 제거한다. --- diff --git a/backend/package-lock.json b/backend/package-lock.json index cc9613e..a346945 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -5817,6 +5817,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, "license": "MIT" }, "node_modules/busboy": { @@ -6338,21 +6339,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } - }, "node_modules/content-disposition": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", @@ -9942,14 +9928,13 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz", - "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.4.0.tgz", + "integrity": "sha512-7dqa0ZcFfzbefdTuIkzOSMvZWC0J7FLqBOjJUZvDCXShIURWKxAyTT1wHhnE5q19c7jOJf43IYYKjBmZVZmvhg==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", - "concat-stream": "^2.0.0", "type-is": "^1.6.18" }, "engines": { @@ -12508,12 +12493,6 @@ "node": ">= 0.4" } }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "license": "MIT" - }, "node_modules/typeorm": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.31.tgz", diff --git a/backend/package.json b/backend/package.json index b3787b5..3af004c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -77,6 +77,9 @@ "typescript": "^5.7.3", "typescript-eslint": "^8.20.0" }, + "overrides": { + "multer": "^2.4.0" + }, "jest": { "moduleFileExtensions": [ "js",