From aae76295625777d4ac7a606bb9a1d7431612af9b Mon Sep 17 00:00:00 2001 From: art Date: Mon, 29 Jun 2026 19:10:42 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20B2=C2=B7B4=C2=B7FE=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20+=20OWASP/multipart=20=EA=B0=80=EC=9D=B4=EB=93=9C=C2=B7dev?= =?UTF-8?q?=20prefetch=20=EB=85=B8=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - security-remediation-checklist §B2·§B4 완료조건 [x] 갱신 - 신규: owasp-dependency-check-guide, multipart-size-risk, changes/work-log 기록 - local-dev-setup: SNAPSHOT→GA 후 online plugin prefetch 함정 노트 - analysis: 제거된 dead code(abstracts/GameCatalog/header.jspf) stale 정정 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01K3FeMrbtxfTScjrwUukyHD --- docs/analysis/2026-06-16-project-analysis.md | 10 +- docs/changes/2026-06-29-b2-b4-fe-hardening.md | 95 +++++++++++++++++++ docs/changes/index.md | 1 + docs/development/local-dev-setup.md | 13 +++ docs/security/index.md | 4 +- docs/security/multipart-size-risk.md | 48 ++++++++++ docs/security/owasp-dependency-check-guide.md | 92 ++++++++++++++++++ .../security-remediation-checklist.md | 68 ++++++++----- docs/work-log/2026-06-29-b2-b4-fe-session.md | 56 +++++++++++ docs/work-log/index.md | 1 + 10 files changed, 360 insertions(+), 28 deletions(-) create mode 100644 docs/changes/2026-06-29-b2-b4-fe-hardening.md create mode 100644 docs/security/multipart-size-risk.md create mode 100644 docs/security/owasp-dependency-check-guide.md create mode 100644 docs/work-log/2026-06-29-b2-b4-fe-session.md diff --git a/docs/analysis/2026-06-16-project-analysis.md b/docs/analysis/2026-06-16-project-analysis.md index 5146d79..52fd7d2 100644 --- a/docs/analysis/2026-06-16-project-analysis.md +++ b/docs/analysis/2026-06-16-project-analysis.md @@ -30,9 +30,9 @@ Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read | 1 | INFO(긍정) | **SQL 인젝션 표면 0** — 6개 매퍼 전 쿼리가 파라미터 바인딩 `#{}` 만 사용. `${}` 문자열 보간 단 1건도 없음(java/xml 전수 grep). 검색어도 `ILIKE CONCAT('%', #{query}, '%')` 로 안전. | mapper/*.java 전체 | | 2 | INFO(긍정) | **비밀번호 해싱 견고** — PBKDF2WithHmacSHA256, 210,000 iterations, 16B salt, 256bit, `MessageDigest.isEqual`(상수시간 비교). 형식 `pbkdf2_sha256$iter$salt$hash`. | UserController.java:542-576 | | 3 | MED | **POST /login, /signup CSRF 미검증** — logout/profile/game/recruit 모든 mutation 은 `CsrfTokens.isValid()` 검사하나 login·signup 은 누락 → login-CSRF 가능. | UserController.java:118-160(login), 65-116(signup) | -| 4 | MED(부채) | **abstracts 패키지 전체 dead code** — `Service`/`Request`/`Result`/`ErrorResult` 어디서도 상속·호출 안 됨. `Service.ChackService` 는 세션키 `"id"`(line 12) 사용, 활성 코드는 `"userId"` 사용 → 재사용 시 인증 우회 함정. | abstracts/Service.java:12 | -| 5 | MED(부채) | **orphan 깨진 JSP fragment** — `WEB-INF/jsp/fragments/header.jspf` 가 Spring Security 태그(``, `${_csrf}`)와 JSTL 사용하나 (a)어디서도 include 안 됨 (b)Spring Security 의존성 자체가 pom 에 없음 (c)라우트(`/games/new`,`/register`)·브랜딩('비빔밥')이 활성앱(`/game/new`,`/signup`,'bibimbap')과 불일치. 이전 프로토타입 잔재. | jsp/fragments/header.jspf:1-25 | -| 6 | MED(부채) | **GameCatalog 완전 stub** — 모든 배열이 `{}` 빈 배열, `COUNT=0`. `GameController.gameDetail` 의 폴백 분기(line 111-128)는 영구 도달 불가(`isValidId` 는 항상 false). 죽은 분기 + 죽은 클래스. | game/GameCatalog.java:8-22, controller/api/GameController.java:111-128 | +| 4 | MED(부채) | **abstracts 패키지 전체 dead code** — `Service`/`Request`/`Result`/`ErrorResult` 어디서도 상속·호출 안 됨. `Service.ChackService` 는 세션키 `"id"`(line 12) 사용, 활성 코드는 `"userId"` 사용 → 재사용 시 인증 우회 함정. | abstracts/Service.java:12 | **제거됨(2026-06-29)** — abstracts/ 패키지 4파일 전체 삭제. [changes/2026-06-29-b2-b4-fe-hardening.md](../changes/2026-06-29-b2-b4-fe-hardening.md) | +| 5 | MED(부채) | **orphan 깨진 JSP fragment** — `WEB-INF/jsp/fragments/header.jspf` 가 Spring Security 태그(``, `${_csrf}`)와 JSTL 사용하나 (a)어디서도 include 안 됨 (b)Spring Security 의존성 자체가 pom 에 없음 (c)라우트(`/games/new`,`/register`)·브랜딩('비빔밥')이 활성앱(`/game/new`,`/signup`,'bibimbap')과 불일치. 이전 프로토타입 잔재. | jsp/fragments/header.jspf:1-25 | **제거됨(2026-06-29)** — header.jspf 삭제. [changes/2026-06-29-b2-b4-fe-hardening.md](../changes/2026-06-29-b2-b4-fe-hardening.md) | +| 6 | MED(부채) | **GameCatalog 완전 stub** — 모든 배열이 `{}` 빈 배열, `COUNT=0`. `GameController.gameDetail` 의 폴백 분기(line 111-128)는 영구 도달 불가(`isValidId` 는 항상 false). 죽은 분기 + 죽은 클래스. | game/GameCatalog.java:8-22, controller/api/GameController.java:111-128 | **제거됨(2026-06-29)** — GameCatalog.java 삭제·fallback 분기 제거. 없는 게임 ID = HTTP 404. [changes/2026-06-29-b2-b4-fe-hardening.md](../changes/2026-06-29-b2-b4-fe-hardening.md) | | 7 | MED(부채) | **게임 댓글: DB 스키마·매퍼 존재하나 미연결** — `game_comments` 테이블, `GameCommentsMapper`(get/add/update), `GameCommentData` 모두 존재하지만 **댓글 작성/조회 컨트롤러 엔드포인트 없음**. 실제 댓글은 JSP 내 localStorage 클라이언트 전용(서버 미저장). `softDeleteGameComments` 만 게임 삭제 시 호출됨. | mapper/GameCommentsMapper.java, views/game-detail.jsp:907-1000 | | 8 | LOW | **WebApplicationFirewall/인증필터 부재 — 세션 기반 임시방편 인증** — Spring Security 미사용. 각 컨트롤러가 수동으로 `session.getAttribute("userId")` 체크. 인가는 리소스 소유권 체크(`userId.equals(game.getUserId())`)로 일관 처리됨(양호)하나 중앙 집중 필터 없음 → 신규 엔드포인트에서 체크 누락 위험. | 전 컨트롤러 | | 9 | LOW | **생성 산출물 git 추적** — `src/test/db/dev-to-live-update.sql`(테스트가 생성하는 dev→live 마이그레이션 진단)이 커밋됨. 실제 비밀값은 없고(`-- DATA DIFF ... password_hash` 주석뿐) 비밀 누출 아님. 단 생성물은 비추적이 적절. | src/test/db/dev-to-live-update.sql:54 | @@ -47,12 +47,12 @@ Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read | 패키지 | 역할 | |--------|------| | (root) | `BibimbapApplication`(@SpringBootApplication main), `ServletInitializer`(WAR 배포용 SpringBootServletInitializer) | -| abstracts | **dead code** — 미사용 제네릭 Service/Request/Result/ErrorResult 베이스 (확인됨: 참조 0) | +| abstracts | **dead code** — 미사용 제네릭 Service/Request/Result/ErrorResult 베이스 (확인됨: 참조 0) → **패키지 전체 제거됨(2026-06-29)** | | config | `UploadResourceConfig` — 업로드 경로의 `/profile/**` 정적 리소스 핸들러 등록 | | controller | 페이지(뷰) 컨트롤러 — `WebMvcController`(공통 페이지+에러), `RecruitController`(모집 페이지+API 혼합) | | controller/api | API/mutation 컨트롤러 — Game CRUD, GameUpload(zip/썸네일/파일), GameAsset(WebGL 서빙), User(인증/프로필), 예외 핸들러 | | data | 순수 POJO DTO 6종 (Lombok 미사용 — 수동 getter/setter) | -| game | `GameCatalog` — **stub(빈 배열)**, 레거시 정적 게임 카탈로그 폴백(현재 무력) | +| game | `GameCatalog` — **stub(빈 배열)**, 레거시 정적 게임 카탈로그 폴백(현재 무력) → **삭제됨(2026-06-29). 없는 게임 ID = HTTP 404** | | mapper | MyBatis `@Mapper` 인터페이스 6종 — 어노테이션 기반 SQL(XML 매퍼 0) | | security | `CsrfTokens` — 세션 기반 CSRF 토큰 발급/검증 유틸(static) | diff --git a/docs/changes/2026-06-29-b2-b4-fe-hardening.md b/docs/changes/2026-06-29-b2-b4-fe-hardening.md new file mode 100644 index 0000000..4f3d906 --- /dev/null +++ b/docs/changes/2026-06-29-b2-b4-fe-hardening.md @@ -0,0 +1,95 @@ +# 2026-06-29 B2·B4·FE 하드닝 + +**세션**: sid 20260629-175705 (resumed from 20260629-174807) +**브랜치**: feat/v2 +**최종 테스트**: `mvn test` 361 PASS + +## 요약 + +3트랙 병렬 작업: B2 프로토타입 dead code 제거, B4 의존성/세션/운영 하드닝, FE 프론트엔드 디자인 개선. + +--- + +## B2. 프로토타입 dead code 제거 + +### 삭제된 파일 + +| 파일 | 이유 | +| --- | --- | +| `src/main/java/com/pandoli365/bibimbap/abstracts/Service.java` | 참조 0, 세션키 `"id"` 불일치(활성코드는 `"userId"`) — 인증 우회 함정 | +| `src/main/java/com/pandoli365/bibimbap/abstracts/Request.java` | 참조 0, dead | +| `src/main/java/com/pandoli365/bibimbap/abstracts/Result.java` | 참조 0, dead | +| `src/main/java/com/pandoli365/bibimbap/abstracts/ErrorResult.java` | 참조 0, dead | +| `src/main/webapp/WEB-INF/jsp/fragments/header.jspf` | include 참조 0, Spring Security 의존성 없는 환경에서의 orphan | +| `src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java` | 빈 배열 stub, 영구 도달불가 분기 | + +### 동작 변경 + +- **없는 게임 ID 접근**: 기존 `GameCatalog` fallback(redirect:/ 또는 정적뷰) → **HTTP 404** (`ResponseStatusException(NOT_FOUND)`). 사용자 확정(D-B2-404). +- `ApiExceptionControllerAdvice` 에 `ResponseStatusException` 핸들러 추가 — 기존 `Exception` catch-all 이 404를 500으로 덮던 결함 수정. + +### 테스트 + +- `GameLikeControllerTest.gameDetailThrowsNotFoundWhenGameMissing` 회귀 가드 추가. +- 런타임 스모크: `GET /game/99999` = 404, `GET /game/3` = 200. + +--- + +## B4. 의존성/세션/운영 하드닝 + +### pom.xml + +| 변경 | 이전 | 이후 | +| --- | --- | --- | +| Spring Boot 버전 | `3.5.14-SNAPSHOT` | `3.5.16` (GA) | +| spring-snapshots repository | 있음 | **제거** | +| spring-snapshots pluginRepository | 있음 | **제거** | +| OWASP Dependency-Check plugin | 없음 | `12.2.2` 추가(빌드 비bind, 수동 실행) | + +### application.properties (공통 base) + +| 변경 | 이전 | 이후 | +| --- | --- | --- | +| ibatis/mybatis 로그 | `TRACE` | `WARN` | +| 세션 쿠키 HttpOnly | 미설정 | `true` | +| 세션 쿠키 SameSite | 미설정 | `lax` | + +### 신규 파일 + +- `src/main/resources/application-dev.properties` — ibatis `TRACE` 로그 복원(로컬 개발 전용). +- `src/main/resources/application-live.properties` — `server.servlet.session.cookie.secure=true` (운영 전용). + +### 런타임 확인 + +`Set-Cookie: JSESSIONID=...; Path=/; HttpOnly; SameSite=Lax` 확인. + +### 결정 기록 + +- D-B4-cfgloc: 설정 위치 = Spring 표준 `application-{profile}.properties`. 기존 `db.properties`(DB 전용) 공존. +- D-B4-multipart: 1GB 유지, 리스크 문서화 → [security/multipart-size-risk.md](../security/multipart-size-risk.md). +- D-B4-cve: OWASP Dependency-Check 오프라인 비bind → [security/owasp-dependency-check-guide.md](../security/owasp-dependency-check-guide.md). + +--- + +## FE. 프론트엔드 디자인 개선 + +### 변경 내용 + +| 항목 | 변경 | +| --- | --- | +| F1 — index 검색 위계 | `.advanced-search` 를 `
` 접이식으로 래핑. prefill(쿼리 파라미터) 시 `open` 속성 자동 부여 | +| F2 — 다크모드 input 대비 | 입력 테두리 `border-color: rgba(255,255,255,0.26)` 강화. 적용 뷰: login·signup·index·game-detail | +| F5 — 터치타깃 | 체크박스 font-size `1.15rem`. 적용 뷰: login·signup | + +### 확인 + +브라우저 스모크: 다크모드 토글 후 input 대비 개선, 검색 접이식 동작, 터치타깃 확대 확인. + +--- + +## 참조 + +- 보안 체크리스트: [security/security-remediation-checklist.md](../security/security-remediation-checklist.md) +- OWASP DC 가이드: [security/owasp-dependency-check-guide.md](../security/owasp-dependency-check-guide.md) +- Multipart 리스크: [security/multipart-size-risk.md](../security/multipart-size-risk.md) +- Local dev 함정: [development/local-dev-setup.md](../development/local-dev-setup.md) diff --git a/docs/changes/index.md b/docs/changes/index.md index ccc7512..e477025 100644 --- a/docs/changes/index.md +++ b/docs/changes/index.md @@ -9,3 +9,4 @@ - [2026-06-29-w3-w4-features.md](./2026-06-29-w3-w4-features.md) — W3(잔여)+W4 5기능 구현(W3-1·W3-4·W3-3·W3-5·W4) + 후속(20260629-142115) 배지 표시 배선 완료·보안 하드닝 b1/b2. 태그+검색·메인허브·포스팅보드·업로드보안·배지/평판 구현(5 커밋 35f1dc3~305cc73, 최종 L1 347/347 GREEN). 후속: 게임카드 creator 배지 칩·프로필 myBadges 배선(3d10449, L1 352/352) + SsrfSafeFetcher @PostConstruct DNS ttl=30(b1 best-effort) + 업로드 저장루트 static 밖 이전(b2, 9041bb7, L1 353/353). DB DDL 8종 사용자 직접 적용 완료. L3 스모크 및 b2 자산 수동 이전은 배포 후 과제 이월 — 상세: [maintenance/post-deploy-verification-checklist.md](../maintenance/post-deploy-verification-checklist.md). - [2026-06-29-game-like-server-persistence.md](./2026-06-29-game-like-server-persistence.md) — 게임 "좋아요" 서버 영속화. 좋아요가 서버에 안 써지고 localStorage 로만 토글되던 버그 수정. 신규 `POST /game/{id}/like` 토글(@Transactional, CSRF→로그인→존재 게이트) + game_likes row/`games.like_count` 컬럼 단일 트랜잭션 ±1 동기 + 상세 JSP fetch 전환 + explore 무변경(컬럼 자동반영). L1 PASS(GameLikeControllerTest 7/7 + 회귀 219/219), L2 dev DB skip. UNIQUE 마이그레이션(20260629-game-likes-unique.sql) 운영 적용·실환경 스모크는 needs_user_verification — 절차: [maintenance/post-deploy-verification-checklist.md](../maintenance/post-deploy-verification-checklist.md). 좋아요 항목으로 보안 B3 부분 충족. - [2026-06-24-w2-jam-platform.md](./2026-06-24-w2-jam-platform.md) — W2 게임잼 워크스트림 전체(W2-1~6) 구현. 잼 엔티티/라이프사이클(jams/jam_teams/jam_team_members/jam_entries/jam_status_log) + 심사위원 역할(jam_judges 잼스코프) + 평가 동결 스키마(jam_criteria/jam_scores/jam_votes/jam_awards + jam_score_stats VIEW, 평가단위 (jam_id,game_id) 활성 자연키) + 심사 평가(3중게이트 UPSERT 가중집계) + 인기투표(1인1표 UNIQUE·종료후 공개) + 시상 집계(3트랙+가중 GRAND·CLOSED 확정 멱등). GAME_JAM_MANAGE 첫 enforcement 연결·잼스코프 isJudge·평가기간 게이트·CSRF 전수. 6 커밋 ccf1e42~a74bf74. 최종 L1 190/190 GREEN(회귀 0), L2 dev DB contract 전 PASS(격리 throwaway DB). dev DB 마이그레이션·L3 스모크 needs_user_verification. +- [2026-06-29-b2-b4-fe-hardening.md](./2026-06-29-b2-b4-fe-hardening.md) — B2 프로토타입 dead code 제거(abstracts 4파일·header.jspf·GameCatalog 삭제, 없는 게임 ID = HTTP 404) + B4 의존성/세션 하드닝(Spring Boot 3.5.16 GA·snapshot repo 제거·쿠키 HttpOnly/SameSite·로그 WARN·OWASP DC plugin 비bind·profile properties 신규) + FE 다크모드 대비·검색 접이식·터치타깃. mvn test 361 PASS. diff --git a/docs/development/local-dev-setup.md b/docs/development/local-dev-setup.md index 7324cbb..36e34f5 100644 --- a/docs/development/local-dev-setup.md +++ b/docs/development/local-dev-setup.md @@ -35,6 +35,19 @@ - **Java 코드**: `docker compose restart app` (spring-boot:run 재기동 ~1~2s). 또는 `docker compose exec app mvn -o -P dev -DskipTests compile` 후 restart. - 전제: 호스트 `~/.m2` 에 의존성 캐시 존재(offline `-o`). **새 의존성 추가 시**: override 의 `-o` 를 일시 제거해 1회 온라인 받거나, 호스트에서 `./mvnw -P dev dependency:go-offline` 후 다시 offline. +> **의존성/플러그인 버전 변경 후 online prefetch 필요** (함정, 2026-06-29 확인): +> SNAPSHOT→GA 등 버전 변경 후 offline 모드(`-o`)로 첫 기동 시 `spring-boot-maven-plugin` 신 버전 resolution 실패 → 컨테이너 즉시 종료. 컨테이너가 죽으면 `exec` 진입 불가. +> +> **처치**: 버전 변경 후 첫 기동 전 아래 중 하나로 온라인 prefetch 1회 수행. +> ```bash +> # 방법 A — run --rm 으로 플러그인·의존성 prefetch (컨테이너 내부, 호스트 ~/.m2 마운트됨) +> docker compose run --rm app mvn -B dependency:resolve-plugins dependency:resolve -P dev +> +> # 방법 B — 호스트에서 mvnw 직접 실행 (호스트 JDK 21 필요) +> ./mvnw -B -P dev dependency:resolve-plugins dependency:resolve +> ``` +> prefetch 성공 후 `docker compose up -d app` 으로 offline 재기동하면 정상 동작. + - **배포 이미지**: override 를 **제외**하고 base 만 명시한다(자동병합 회피). ```bash docker compose -f docker-compose.yml build app # Dockerfile WAR 굽기 diff --git a/docs/security/index.md b/docs/security/index.md index 62c8181..121e306 100644 --- a/docs/security/index.md +++ b/docs/security/index.md @@ -4,4 +4,6 @@ ## 목록 -_(아직 문서 없음)_ +- [security-remediation-checklist.md](./security-remediation-checklist.md) — 보안 개선 체크리스트 (B1~B4 항목별 근거·체크박스·완료 조건). 기준 분석: 2026-06-16 프로젝트 전면 분석. +- [owasp-dependency-check-guide.md](./owasp-dependency-check-guide.md) — OWASP Dependency-Check maven plugin 운영 가이드. 수동 실행·오프라인 모드·빌드 비bind 이유·예외 기준(CVSS 7+). (B4, 2026-06-29) +- [multipart-size-risk.md](./multipart-size-risk.md) — multipart 업로드 1GB 상한 유지 결정 및 DoS/디스크 고갈 리스크 문서화. 완화 권고(리버스프록시·모니터링) 포함. (B4, 2026-06-29) diff --git a/docs/security/multipart-size-risk.md b/docs/security/multipart-size-risk.md new file mode 100644 index 0000000..5a4a129 --- /dev/null +++ b/docs/security/multipart-size-risk.md @@ -0,0 +1,48 @@ +# Multipart 업로드 1GB 상한 유지 — 리스크 문서화 + +**결정**: multipart 상한 1GB 유지 (사용자 확정, D-B4-multipart, sid 20260629-175705) +**관련 체크리스트**: [security-remediation-checklist.md §B4](./security-remediation-checklist.md#b4-의존성세션운영-하드닝) + +## 현재 설정 + +`src/main/resources/application.properties`: + +```properties +spring.servlet.multipart.max-file-size=1GB +spring.servlet.multipart.max-request-size=1GB +``` + +## 리스크 + +### DoS (Denial of Service) + +- 악의적 클라이언트가 1GB 파일 업로드를 동시에 여러 건 요청하면 서버 처리 스레드·메모리·디스크 I/O를 소진할 수 있다. +- Spring MVC 기본 설정에서 multipart 파싱은 요청 완료 전까지 임시 파일(`java.io.tmpdir`)에 버퍼링된다. +- 임시 디스크 고갈 시 다른 기능(로그, 쓰기 연산)도 함께 영향받는다. + +### 디스크 고갈 + +- 업로드 임시 경로(`/tmp` 또는 컨테이너 tmpfs)와 저장 경로(`~/.bibimbap/uploads`)가 모두 대용량 파일로 채워질 수 있다. +- 컨테이너 환경에서 tmpfs 상한이 낮으면 1GB 단일 파일에도 OOM 오류가 발생한다. + +## 유지 결정 근거 + +- bibimbap 은 게임 WebGL 번들 업로드를 지원하며, 실제 게임 빌드 크기가 수백 MB에 이를 수 있다. 상한을 낮추면 합법적 업로드가 차단된다. +- 현재 운영 규모(소규모)에서 대규모 DoS 시도 가능성이 낮다고 사용자가 판단했다. +- 상한 축소·업로드 스트리밍 개선은 이후 운영 부하 확인 후 별도 검토. + +## 완화 권고 (추후) + +| 권고 항목 | 설명 | +| --- | --- | +| 리버스 프록시 상한 설정 | nginx `client_max_body_size` 또는 AWS ALB 요청 크기 제한으로 애플리케이션 앞단 차단 | +| 디스크 모니터링 | 업로드 경로(`~/.bibimbap/uploads`) 및 tmpdir 사용량 alert 설정 (예: 80% 도달 시 알림) | +| 업로드 인증 게이트 | 현재 게임 업로드는 로그인 필수 — 익명 대용량 업로드 차단. 권한 검사 유지 확인. | +| 동시 업로드 rate limit | Spring MVC thread pool 또는 리버스 프록시 connection limit으로 동시 대용량 요청 제한 | +| tmpdir 분리 | `spring.servlet.multipart.location` 으로 tmpdir를 전용 볼륨으로 분리해 시스템 영향 격리 | + +## 관련 문서 + +- [owasp-dependency-check-guide.md](./owasp-dependency-check-guide.md) — 동일 B4 트랙 CVE 스캔 설정 +- [security-remediation-checklist.md](./security-remediation-checklist.md) — 전체 보안 체크리스트 +- [../maintenance/post-deploy-verification-checklist.md](../maintenance/post-deploy-verification-checklist.md) — 배포 후 디스크 확인 절차 diff --git a/docs/security/owasp-dependency-check-guide.md b/docs/security/owasp-dependency-check-guide.md new file mode 100644 index 0000000..ff23567 --- /dev/null +++ b/docs/security/owasp-dependency-check-guide.md @@ -0,0 +1,92 @@ +# OWASP Dependency-Check 운영 가이드 + +**적용 범위**: bibimbap `pom.xml` — `dependency-check-maven 12.2.2` +**결정 출처**: D-B4-cve (sid 20260629-175705) +**관련 체크리스트**: [security-remediation-checklist.md §B4](./security-remediation-checklist.md#b4-의존성세션운영-하드닝) + +## 설정 개요 + +```xml + + org.owasp + dependency-check-maven + 12.2.2 + + false + 7 + true + + + +``` + +## 수동 실행 + +### 온라인 모드 (NVD 데이터 갱신 포함) + +```bash +mvn dependency-check:check +``` + +NVD API 키가 없으면 요청 제한에 걸려 매우 느릴 수 있다. 처음 실행 시 수 분 소요 정상. + +### 오프라인 모드 (캐시 재사용) + +`autoUpdate=false`가 기본이므로 플러그인 설정 그대로 실행하면 로컬 캐시를 사용한다. + +```bash +mvn dependency-check:check +``` + +캐시 위치: `~/.m2/repository/org/owasp/dependency-check-data/` (기본값). +캐시가 없으면 온라인 다운로드가 강제된다. + +캐시를 명시적으로 지정하려면: + +```bash +mvn dependency-check:check -Ddependency-check.dataDirectory=/path/to/cache +``` + +### 도커 dev 환경 + +```bash +docker compose exec app mvn dependency-check:check +``` + +호스트 `~/.m2` 가 마운트되어 있으면 캐시를 공유한다. + +## 예외 기준 + +| 설정 | 값 | 설명 | +| --- | --- | --- | +| `failBuildOnCVSS` | `7` | CVSS 7.0 이상 high/critical만 빌드 실패 유발 | +| `skipProvidedScope` | `true` | WAS/컨테이너 제공 라이브러리(서블릿 API 등) 제외 | + +CVSS 7 미만(low/medium) CVE는 보고서에 기록되지만 빌드를 실패시키지 않는다. +특정 CVE를 false positive로 억제하려면 suppression 파일 사용: + +```bash +mvn dependency-check:check -Ddependency-check.suppressionFile=owasp-suppressions.xml +``` + +## 빌드 phase 비bind 이유 + +`executions` 블록을 두지 않아 일반 `mvn package`·`mvn test` 에 포함되지 않는다. + +**이유**: +1. NVD 데이터 갱신/조회에 수 분이 소요되어 CI 빌드 시간 급증. +2. `autoUpdate=false` 오프라인 모드에서도 스캔 자체 시간이 30초~수 분. +3. CVE 결과는 즉각 hotfix가 아닌 우선순위 검토·예외 결정을 필요로 한다 — 빌드 자동 실패보다 수동 검토가 적합. +4. 로컬 dev 캐시 없는 환경(CI 클린 이미지)에서 온라인 의존성 생김. + +**권장 주기**: 배포 전 수동 1회 실행 → 보고서 검토 → CVSS 7+ 항목 처리 후 배포. + +## 보고서 위치 + +실행 후 `target/dependency-check-report.html` 생성. +콘솔에서 HIGH/CRITICAL 요약 출력됨. + +## 관련 문서 + +- [multipart-size-risk.md](./multipart-size-risk.md) — 1GB 유지 리스크 (동일 B4 트랙) +- [security-remediation-checklist.md](./security-remediation-checklist.md) — 전체 보안 체크리스트 diff --git a/docs/security/security-remediation-checklist.md b/docs/security/security-remediation-checklist.md index 277e805..c63fbbd 100644 --- a/docs/security/security-remediation-checklist.md +++ b/docs/security/security-remediation-checklist.md @@ -66,20 +66,32 @@ - `GameCatalog`는 빈 배열 기반 fallback이다. `src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java:8`, `src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java:18` - `GameController`는 DB 게임이 없으면 `GameCatalog` fallback을 시도한다. `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:111`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:116` +구현 결과 (2026-06-29, sid 20260629-175705): + +- `abstracts/` 패키지 4파일(`Service`, `Request`, `Result`, `ErrorResult`) 전체 삭제. 참조 0 확인 후 제거. +- `src/main/webapp/WEB-INF/jsp/fragments/header.jspf` 삭제. include 참조 0 확인 후 제거. +- `src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java` 삭제. +- `GameController.gameDetail()` 의 GameCatalog fallback(redirect:/+정적뷰) 제거 → DB 미존재 게임 ID = **HTTP 404** (`ResponseStatusException(NOT_FOUND)`). 사용자 확정 결정(D-B2-404). +- `ApiExceptionControllerAdvice` 에 `ResponseStatusException` 핸들러 추가 — 기존 `Exception` catch-all 이 500으로 덮던 문제 수정, 404 status 보존. +- 회귀 가드 `GameLikeControllerTest.gameDetailThrowsNotFoundWhenGameMissing` 추가. +- `mvn test` 361 PASS. 런타임 스모크: `GET /game/99999` = 404, `GET /game/3` = 200 확인. + 체크리스트: -- [ ] `rg "abstracts|GameCatalog|header.jspf"`로 실제 참조를 재확인한다. -- [ ] `abstracts` 패키지를 삭제해도 컴파일이 깨지지 않는지 확인한다. -- [ ] `header.jspf` 삭제 전 JSP include 경로가 전부 `/WEB-INF/views/header.jsp`인지 확인한다. -- [ ] `GameCatalog` fallback 제거 시 없는 게임 ID의 기대 동작을 `redirect:/` 또는 404로 결정한다. -- [ ] 삭제 PR에는 기능 변경이 없도록 테스트와 수동 확인 범위를 좁힌다. -- [ ] 문서에서 제거된 프로토타입 흐름을 최신 구조로 갱신한다. +- [x] `rg “abstracts|GameCatalog|header.jspf”`로 실제 참조를 재확인한다. +- [x] `abstracts` 패키지를 삭제해도 컴파일이 깨지지 않는지 확인한다. +- [x] `header.jspf` 삭제 전 JSP include 경로가 전부 `/WEB-INF/views/header.jsp`인지 확인한다. +- [x] `GameCatalog` fallback 제거 시 없는 게임 ID의 기대 동작을 `redirect:/` 또는 404로 결정한다. → **HTTP 404 확정**. +- [x] 삭제 PR에는 기능 변경이 없도록 테스트와 수동 확인 범위를 좁힌다. +- [x] 문서에서 제거된 프로토타입 흐름을 최신 구조로 갱신한다. → analysis 문서 stale 항목 갱신 완료. 완료 조건: -- [ ] dead code 파일이 제거되거나 “보존 이유”가 문서화된다. -- [ ] `mvn test` 또는 최소 컴파일 검증이 통과한다. -- [ ] 없는 게임 상세 접근의 동작이 명확하다. +- [x] dead code 파일이 제거되거나 “보존 이유”가 문서화된다. → abstracts 4파일·header.jspf·GameCatalog.java 삭제. +- [x] `mvn test` 또는 최소 컴파일 검증이 통과한다. → 361 PASS. +- [x] 없는 게임 상세 접근의 동작이 명확하다. → **HTTP 404** (ResponseStatusException(NOT_FOUND)). + +> 구현 이력: [changes/2026-06-29-b2-b4-fe-hardening.md](../changes/2026-06-29-b2-b4-fe-hardening.md) ## B3. 좋아요/댓글 서버 영속화 연결 @@ -131,24 +143,36 @@ - MyBatis TRACE 로그가 켜져 있다. `src/main/resources/application.properties:25`, `src/main/resources/application.properties:26`, `src/main/resources/application.properties:27` - multipart 상한이 1GB다. `src/main/resources/application.properties:15`, `src/main/resources/application.properties:16`, `src/main/resources/application.properties:17`, `src/main/resources/application.properties:18` +구현 결과 (2026-06-29, sid 20260629-175705): + +- **Spring Boot**: `3.5.14-SNAPSHOT` → **`3.5.16` (GA)** 고정. `pom.xml` spring-boot.version 갱신. +- **Snapshot repo 제거**: `pom.xml` 의 spring-snapshots repository·pluginRepository 블록 삭제. snapshot 없이 `mvn -B dependency:resolve` PASS 확인. +- **쿠키 하드닝**: `application.properties` 에 `server.servlet.session.cookie.http-only=true`, `server.servlet.session.cookie.same-site=lax` 추가. `application-live.properties`(신규) 에 `server.servlet.session.cookie.secure=true`. 런타임 `Set-Cookie` 확인: `JSESSIONID; HttpOnly; SameSite=Lax`. +- **MyBatis 로그**: `application.properties` ibatis TRACE → **WARN**. `application-dev.properties`(신규) 에 TRACE 복원(로컬 개발 전용). +- **Profile properties**: Spring 표준 `application-dev.properties`·`application-live.properties` 신규 생성. 기존 `db.properties`(DB 전용) 공존. 사용자 합의(D-B4-cfgloc). +- **OWASP Dependency-Check**: `pom.xml` 에 `dependency-check-maven 12.2.2` plugin 추가. executions 없음(빌드 phase 비bind). 수동 실행: `mvn dependency-check:check`. autoUpdate=false, failBuildOnCVSS=7, skipProvidedScope=true. 상세: [security/owasp-dependency-check-guide.md](./owasp-dependency-check-guide.md). +- **multipart 1GB 유지**: 사용자 확정(D-B4-multipart). 리스크 문서화: [security/multipart-size-risk.md](./multipart-size-risk.md). + 체크리스트: -- [ ] Spring Boot를 안정 release 버전으로 고정한다. -- [ ] snapshot repository와 pluginRepository 필요성을 제거하거나 문서화한다. -- [ ] `server.servlet.session.cookie.http-only=true`를 설정한다. -- [ ] live 프로필에서 `server.servlet.session.cookie.secure=true`를 설정한다. -- [ ] `server.servlet.session.cookie.same-site=lax` 또는 stricter 정책을 결정한다. -- [ ] 운영 로그에서 MyBatis TRACE를 낮추고 민감 파라미터 노출 가능성을 확인한다. -- [ ] multipart 상한을 실제 운영 허용치와 맞춘다. -- [ ] 의존성 CVE 스캔 도구를 정한다. 후보: Dependabot, OWASP Dependency-Check, Maven Versions Plugin. -- [ ] CVE 스캔 결과와 예외 처리 기준을 `docs/security/`에 기록한다. +- [x] Spring Boot를 안정 release 버전으로 고정한다. → **3.5.16 GA**. +- [x] snapshot repository와 pluginRepository 필요성을 제거하거나 문서화한다. → spring-snapshots 블록 삭제. +- [x] `server.servlet.session.cookie.http-only=true`를 설정한다. +- [x] live 프로필에서 `server.servlet.session.cookie.secure=true`를 설정한다. → `application-live.properties`. +- [x] `server.servlet.session.cookie.same-site=lax` 또는 stricter 정책을 결정한다. → **lax** 설정. +- [x] 운영 로그에서 MyBatis TRACE를 낮추고 민감 파라미터 노출 가능성을 확인한다. → base WARN, dev 전용 TRACE. +- [x] multipart 상한을 실제 운영 허용치와 맞춘다. → **1GB 유지(사용자 확정), 리스크 문서화 완료**. +- [x] 의존성 CVE 스캔 도구를 정한다. → **OWASP Dependency-Check maven plugin**. +- [x] CVE 스캔 결과와 예외 처리 기준을 `docs/security/`에 기록한다. → [security/owasp-dependency-check-guide.md](./owasp-dependency-check-guide.md). 완료 조건: -- [ ] 빌드가 snapshot repository 없이 재현 가능하다. -- [ ] 운영 쿠키에 HttpOnly/Secure/SameSite 정책이 반영된다. -- [ ] CVE 스캔 결과가 문서화된다. -- [ ] 운영 로그 레벨이 민감 데이터 노출을 최소화한다. +- [x] 빌드가 snapshot repository 없이 재현 가능하다. → `mvn -B dependency:resolve` PASS. +- [x] 운영 쿠키에 HttpOnly/Secure/SameSite 정책이 반영된다. → 런타임 `Set-Cookie` 확인 완료. +- [x] CVE 스캔 결과가 문서화된다. → OWASP DC plugin 설정 + guide 문서화. +- [x] 운영 로그 레벨이 민감 데이터 노출을 최소화한다. → base WARN. + +> 구현 이력: [changes/2026-06-29-b2-b4-fe-hardening.md](../changes/2026-06-29-b2-b4-fe-hardening.md) ## 공통 테스트 매트릭스 diff --git a/docs/work-log/2026-06-29-b2-b4-fe-session.md b/docs/work-log/2026-06-29-b2-b4-fe-session.md new file mode 100644 index 0000000..d31ab82 --- /dev/null +++ b/docs/work-log/2026-06-29-b2-b4-fe-session.md @@ -0,0 +1,56 @@ +# 2026-06-29 B2·B4·FE 작업 세션 핸드오프 + +**sid**: 20260629-175705 (resumed from 20260629-174807) +**완료 상태**: 3트랙 모두 완료·검증 +**브랜치**: feat/v2 +**최종 테스트**: `mvn test` 361 PASS + +## 완료된 트랙 + +### B2 — 프로토타입 dead code 제거 + +- abstracts/ 패키지 4파일 삭제. +- fragments/header.jspf 삭제. +- game/GameCatalog.java 삭제. +- GameController.gameDetail() fallback 제거 → 없는 게임 ID = HTTP 404. +- ApiExceptionControllerAdvice에 ResponseStatusException 핸들러 추가(404 status 보존). +- GameLikeControllerTest.gameDetailThrowsNotFoundWhenGameMissing 추가. +- 런타임 스모크: /game/99999=404, /game/3=200. + +### B4 — 의존성/세션/운영 하드닝 + +- Spring Boot 3.5.14-SNAPSHOT → 3.5.16 GA. +- pom에서 spring-snapshots repository·pluginRepository 제거. +- OWASP dependency-check-maven 12.2.2 비bind 추가. +- application.properties: ibatis TRACE→WARN, HttpOnly·SameSite=Lax 추가. +- application-dev.properties 신규(ibatis TRACE). +- application-live.properties 신규(cookie.secure=true). +- 런타임 Set-Cookie 확인 완료. + +### FE — 프론트엔드 디자인 개선 + +- index 검색 `
` 접이식(prefill 시 open). +- 다크모드 input 테두리 대비 강화(login·signup·index·game-detail). +- 체크박스 터치타깃 1.15rem(login·signup). +- 브라우저 스모크 확인. + +## 주요 결정 + +| ID | 결정 | +| --- | --- | +| D-B2-404 | 없는 게임 ID = HTTP 404 (ResponseStatusException(NOT_FOUND)). 사용자 확정. | +| D-B4-cve | CVE 스캔 = OWASP Dependency-Check 비bind. autoUpdate=false, CVSS>=7 실패. 사용자 확정. | +| D-B4-multipart | multipart 1GB 유지. 리스크 문서화: docs/security/multipart-size-risk.md. 사용자 확정. | +| D-B4-cfgloc | application-{profile}.properties 신규. db.properties 공존. | +| D-test-mockfix | BibimbapApplicationTests GameLikesMapper @MockBean 누락(B3 사전결함) 1줄 보강. | + +## 이월 없음 + +이번 세션의 open_items(dev override online prefetch 함정)는 development/local-dev-setup.md에 노트 추가 완료. + +## 참조 + +- 구현 이력: [changes/2026-06-29-b2-b4-fe-hardening.md](../changes/2026-06-29-b2-b4-fe-hardening.md) +- 보안 체크리스트: [security/security-remediation-checklist.md](../security/security-remediation-checklist.md) +- OWASP DC: [security/owasp-dependency-check-guide.md](../security/owasp-dependency-check-guide.md) +- Multipart 리스크: [security/multipart-size-risk.md](../security/multipart-size-risk.md) diff --git a/docs/work-log/index.md b/docs/work-log/index.md index 49e71f8..36e998a 100644 --- a/docs/work-log/index.md +++ b/docs/work-log/index.md @@ -8,3 +8,4 @@ - [2026-06-17-w3-feature-skeletons.md](./2026-06-17-w3-feature-skeletons.md) — W3(사이트 플랫폼) 5개 서브기능 골자 카탈로그. 기능당 목적·핵심동작·결합/의존·미결질문·후속진입점(윤곽 깊이, 미결 해소 안 함). 코드 현황 대조(tag/review 테이블 신규·Interceptor 미구현·`/game/**` 핸들러 미등록). 결합표 + 착수순서 제언(W3-2 1순위) + QG-1~3. 당장 구현 아님. `status: confirmed`(20260617-174635 세션서 5기능+착수순서 항목별 합의), source_session: 20260617-172407. ⚠️ **코드 현황 #2·#3 stale (2026-06-23 정정)** — w2-w4-skeletons 참조. - [2026-06-23-w2-w4-full-design-summary.md](./2026-06-23-w2-w4-full-design-summary.md) — **W2+W3(잔여)+W4 풀설계 통합 요약/설계 인덱스**. 11기능(W2-1~6·W3-1/3-3/3-4/3-5·W4) 풀설계(W1-design 깊이, 오픈질문 0) 진입 인덱스 + 크로스-W 결정 매트릭스(정석 확정) + 교차정합 감사 verdict(HIGH 1 해소·동결 단일권위 유지) + 골자 stale 정정 3건 + 착수순서. 설계 본문은 `.atp/work-session/20260623-104307/implementation/`. 설계 전용(코드 0줄). `status: active`, source_session: 20260623-104307, resumed_from: 20260622-180054. - [2026-06-23-w2-w4-feature-skeletons.md](./2026-06-23-w2-w4-feature-skeletons.md) — W2(게임잼 6서브: 잼엔티티/심사위원역할/평가동결/심사평가/인기투표/시상집계) + W4(유저 배지/평판) 골자 카탈로그. W3 skeletons 포맷(목적·핵심동작·결합/의존[code-fact vs 해석 태깅]·미결질문[비해소]·후속진입점) + 결합표 + 착수순서 + QG-W2-A~D·W4-A. **W3 skeletons "코드 현황" stale 2건 정정**(RBAC 게이트·리뷰 인프라 실재 — 단 POST_WRITE/GAME_JAM_MANAGE enforcement 미연결, 리뷰는 하이브리드 6축+VIEW). code grounding: `.atp/work-session/20260623-104307/research/W2-W4-grounding.md`. `status: draft`(합의 전), source_session: 20260623-104307. +- [2026-06-29-b2-b4-fe-session.md](./2026-06-29-b2-b4-fe-session.md) — B2(dead code 제거)·B4(의존성/세션 하드닝)·FE(프론트 개선) 3트랙 완료 핸드오프. 주요 결정 5건(404/OWASP 비bind/multipart 유지/profile properties/mockfix). mvn test 361 PASS. 이월 없음.