20 KiB
| kind | title | session_id | followup_session_id | created_at | status | related_design_summary | related_design | related_impl | related_followup_impl | related_verification | related_followup_verification | related_report | related_followup_report | related_security_checklist | related_prev_change | related_post_deploy |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| change | W3(잔여) + W4 기능 구현 + 배지 표시 배선 + 보안 하드닝 b1/b2 (W3-1·W3-4·W3-3·W3-5·W4 + 20260629-142115) | 20260629-100849 | 20260629-142115 | 2026-06-29 | implemented | ../work-log/2026-06-23-w2-w4-full-design-summary.md | ../../.atp/work-session/20260623-104307/implementation/ | ../../.atp/work-session/20260629-100849/implementation/ | ../../.atp/work-session/20260629-142115/implementation/ | ../../.atp/work-session/20260629-100849/verification.md | ../../.atp/work-session/20260629-142115/verification.md | ../../.atp/work-session/20260629-100849/report.md | ../../.atp/work-session/20260629-142115/report.md | ../security/security-remediation-checklist.md | ./2026-06-24-w2-jam-platform.md | ../maintenance/post-deploy-verification-checklist.md |
W3(잔여) + W4 기능 구현 변경 이력 (W3-1·W3-4·W3-3·W3-5·W4)
상위 설계 인덱스: W2+W3(잔여)+W4 풀설계 통합 요약 §설계 인덱스 W3-1/W3-3/W3-4/W3-5/W4.
설계 본문: .atp/work-session/20260623-104307/implementation/W3-*.md · W4-badges-design.md · 파일 소유권/구현 보고: .atp/work-session/20260629-100849/implementation/ownership*.md · 검증: .atp/work-session/20260629-100849/verification.md · 세션 보고: .atp/work-session/20260629-100849/report.md.
개요
W2 게임잼 워크스트림(2026-06-24-w2-jam-platform.md) 완료 후, W2-W4 풀설계 11기능 중 미구현으로 남았던 잔여 5기능을 순차 구현했다. W3-2(댓글/리뷰)는 이미 완료 상태였고, 본 작업은 태그+검색(W3-1) → 메인허브(W3-4) → 포스팅보드(W3-3) → 업로드보안(W3-5) → 배지/평판(W4) 순서로 각 기능을 impl → verify → commit 롤백 경계로 묶어 진행했다(5 feat 커밋).
| 서브 | 커밋 | 핵심 | L1 누적 |
|---|---|---|---|
| W3-1 태그 + 검색 확장 | 35f1dc3 |
tags/game_tags/jam_tags/game_views + searchGamesAdvanced(다중태그 AND/OR·정렬키6·방문수·리뷰정렬·잼라우트) + TagController(sanitize/금칙어/CONTENT_MODERATE) | 235/235 |
| W3-4 메인 허브 | e28fa60 |
keyset 페이징(혼합방향 OR분해) + 진행중 잼 배너(/games/search?jam=) |
243/243 |
| W3-3 포스팅 보드 | 6047a39 |
posts/categories + POST_WRITE enforcement + commonmark/jsoup sanitize + SsrfSafeFetcher(9항목) + 유니티 피드 폴링(@Scheduled) | 287/287 |
| W3-5 업로드 보안 | 08d191f |
ZipSecurity(zip-slip/심링크/zipbomb/포맷/권한게이트) + ATOMIC_MOVE swap + game_upload_audit_log | 318/318 |
| W4 유저 배지/평판 | 305cc73 |
badges/user_badges/reputation_events + 자동(임계)·수동(BADGE_MANAGE) 하이브리드 + reputation 감사 + 리뷰작성자 표시 | 347/347 |
커밋 범위: 35f1dc3(W3-1) → e28fa60(W3-4) → 6047a39(W3-3) → 08d191f(W3-5) → 305cc73(W4). 모두 feat/v2 브랜치.
신규 DDL 5종: docs/tag-ddl.sql · docs/games-hub-ddl.sql · docs/board-ddl.sql · docs/game-upload-ddl.sql · docs/badge-ddl.sql(전부 db/schema.sql 동기). 신규 pom 의존 2종: jsoup 1.17.2 + commonmark 0.22.0(W3-3).
W3-1 — 태그 + 검색 확장 (35f1dc3)
DDL (신규 4테이블 + games 컬럼 1)
| 객체 | 역할 | 핵심 제약 |
|---|---|---|
tags |
통합 태그 사전 | 정규화 slug 활성 UNIQUE |
game_tags |
게임-태그 조인 | (game_id, tag_id) UNIQUE, #{} 바인딩 |
jam_tags |
잼-태그 조인 | (jam_id, tag_id) UNIQUE |
game_views |
방문수 dedupe 원장 | 최근 interval 내 재방문 제외 근거 |
games.view_count |
누적 방문수 캐시 컬럼 | game_views 적재 시 증가 |
- DDL 권위:
docs/tag-ddl.sql(멱등) +db/schema.sql동기.
런타임 동작
GamesMapper.searchGamesAdvanced— 다중태그 AND/OR 분리(GameTagsMapper.listGameIdsByTagsAll/...Any, AND ⊆ OR 불변), 정렬키 6종, 방문수·리뷰(game_review_stats소비) 정렬, 잼 필터 라우트.SearchCriteriaPOJO 로 파라미터 캡슐화.SearchController—GET /games/search태그·정렬·잼 파라미터 처리(파라미터 전수#{}). index.jsp SSR.GameViewsMapper.existsRecentView— 최근 방문 dedupe(설계 dead paramsinceTs제거).GameController방문 적재 훅.TagController— 태그 생성/부착.TagSanitizer(normalize +banned-words.txt금칙어) + CONTENT_MODERATE 게이트(신규 권한키 미도입, W1 선언키 재사용) + CSRF.
의미보존 편차(verification 인지)
insertTag는int+ generatedKeys 패턴(설계 표현 대비 의미보존).TagController가GamesMapper주입으로 부착 대상 게임 소유자 확인(설계 의도 충족).
검증
L1 235/235 GREEN(baseline 190→235, +45 신규테스트, 회귀 0). L2 격리 throwaway DB: alias 케이스폴딩 인용/비인용 대조, NULLS LAST, AND ⊆ OR, ILIKE, interval dedupe. AC-1/2/4/5 + 보안 VP PASS.
W3-4 — 메인 허브 (e28fa60)
DDL
신규 테이블 0(GamesMapper/JamsMapper 확장만). docs/games-hub-ddl.sql 는 keyset 인덱스 등 허브 지원 객체.
런타임 동작
GamesMapperkeyset 페이징 — 혼합 정렬방향을 OR 분해한 row-comparison 으로 경계 무중복·무누락 보장(검색·비검색 단일화). 단계1(keyset 더보기) + 단계2(진행중 잼 배너) 통합 구현.JamsMapper.listActive(limit=1) — 진행중 잼 1건 배너용.WebMvcController가 hub 에 주입.- 진행중 잼 배너 → W3-1
GET /games/search?jam={slug}라우팅(cross-W 정합).
검증
L1 243/243 GREEN(회귀 0, 편차 0). L2: keyset 혼합방향 OR분해 경계 무중복·무누락·tie-break 실측(throwaway DB). AC-T2/T3/T4 + 단계무결성 + 보안 VP PASS. BibimbapApplicationTests 무변경(GamesMapper/JamsMapper @MockBean 기존 등록 재사용).
W3-3 — 포스팅 보드 (6047a39)
가장 복잡한 워크스트림(SSRF 9항목·md sanitize·다매퍼·@Scheduled·pom 의존). 검증 단계에서 verification + adversarial SSRF 감사 2중 검증이 vacuous(거짓통과) SSRF 결함을 포착해 backward 1회 보정(아래 §SSRF 강화)했다.
DDL (신규 4테이블) + pom 의존
| 객체 | 역할 |
|---|---|
post_categories |
게시판 카테고리 |
posts |
게시글(마크다운 본문) |
unity_feed_sources |
유니티 외부 피드 소스 |
unity_feed_items |
폴링 적재 피드 아이템(dedupe) |
- DDL 권위:
docs/board-ddl.sql(멱등) +db/schema.sql(append) 동기. - pom 신규 의존: jsoup 1.17.2(1.14+ Safelist API 정합) + commonmark 0.22.0. 오프라인 빌드용으로 프록시 CA 주입 1회 pre-resolve 후
~/.m2캐시(이후 오프라인 빌드 가능).
런타임 동작
PostController— 게시글 작성/조회. POST_WRITE enforcement 연결(W1 선언키의 첫 소비처) + CSRF.PostMarkdownService— commonmark 렌더 → jsoup Safelist sanitize(XSS 차단). 검증에서 XSS sanitize SOUND.OgPreviewService/UnityFeedPoller—SsrfSafeFetcher단일 경유 외부 fetch.FeedParser(RSS 2.0 / RFC1123 pubDate).UnityFeedPoller@Scheduled폴링 + dedupe insert(SchedulingConfig@EnableScheduling신규 활성).PostAdminController/UnityFeedAdminController— 카테고리·피드소스 관리(JSP 는 프로젝트 컨벤션 scriptlet +HtmlUtils.htmlEscape로 정렬, JSTL 의존 부재 대응).
SSRF 9항목 (SsrfSafeFetcher)
scheme allowlist · 사설/루프백 IP 차단 · DNS rebinding 방어 · redirect 재검증 · size/timeout 상한 등 9항목. 초기 구현은 Host restricted-header 를 통한 fetch 가 전건 empty 가 되어 테스트가 vacuous(거짓통과) 였고 CGNAT(100.64/10) 등 차단 범위갭이 있었다. backward 보정에서:
- option-b hostname-connect 전환 — 사전 resolve 한 IP 를 차단 검사 후 hostname 으로 직접 connect(restricted-header 우회 제거).
- 차단 대역 확장(CGNAT 등).
FeedParserRFC1123 pubDate 파싱 robust 화(선행 요일토큰 요일-날짜 불일치에도 견딤 — orchestrator 마이크로수정).
검증
backward: verification FAIL(L1 283, 2fail+1err) + adversarial SSRF 감사 CRITICAL → impl fix1 → 재검증 FAIL(FeedParser 잔존) → orchestrator 마이크로수정 → L1 287/287 GREEN. L2: board keyset row-comparison · insertIgnoreDup ON CONFLICT · FK(throwaway DB). adversarial 감사: XSS SOUND, 보정 후 SSRF 우회 차단.
W3-5 — Unity WebGL 업로드 보안 보강 (08d191f)
DDL (신규 1테이블)
game_upload_audit_log— 업로드 감사(outcome CHECK, FK, reject_reason enum).docs/game-upload-ddl.sql(멱등) +db/schema.sql(append) 동기.
런타임 동작
ZipSecurity(+ZipRejectException) — zip-slip canonical 경계 + 심링크 거부 + zip bomb(getCompressedSize 0 회피·3중상한) + 포맷검증(wasm/data/loader) + 권한게이트. 거부 사유 enum 11종.GameUploadController— 게이트 순서 정렬 + 임시추출 후ATOMIC_MOVEswap(원자 교체) + audit 적재. dead code 제거.GameUploadAuditMapper(+GameUploadAudit/GameAssetOwnerPOJO) —#{}only, 컬럼 8 정합.GameAssetCleanupService— 자산 생명주기 정리(UUID 검증 + 경계, gameRoot 미터치).
검증
L1 318/318 GREEN(편차 0). L2: audit insert · outcome CHECK · FK · findGameByWebglUuid alias(throwaway DB). adversarial zip-slip 감사 SOUND — 심링크 TOCTOU 구조불가, 경로탈출/유니코드/zipbomb/LIKE주입 전수 차단. LOW 2건(split 빈세그먼트·static 트리 .tmp 노출)은 open_item.
W4 — 유저 배지 / 평판 (305cc73)
DDL (신규 3테이블) + 권한키
| 객체 | 역할 | 핵심 제약 |
|---|---|---|
badges |
배지 카탈로그 | badges_type_check |
user_badges |
유저 보유 배지 | ux_user_badges_active 부분유니크(revoked 행 제외 → 회수후 재획득) |
reputation_events |
평판 이벤트 감사 | reputation_events_type_check IN(REVIEW_WRITTEN, GAME_UPLOADED), source_ref 부분유니크 dedupe |
- DDL 권위:
docs/badge-ddl.sql(멱등) +db/schema.sql(line 1075/1100/1127) 동기. PermissionKeys.BADGE_MANAGE신규(4번째 권한키) — W1 권한 카탈로그 카운트 3→4 갱신.PermissionCatalogVerifier가values()순회(하드코딩 리스트 아님)라 키 추가에 자동 적응, 카운트 가정 테스트 회귀 0.
런타임 동작
BadgeService/ReputationService— 자동(임계 도달 시 부여)·수동(grant/revoke) 하이브리드.BadgeCatalogSeeder시드.- 자동 부여 훅 2곳:
GameReviewController.listReviews경로(리뷰 작성) +GameController게임 업로드 경로 →reputationService.record. event_type CHECK 2종과 정합. BadgeManageController—/manage/badgesgrant/revoke. 진입부 순서 CSRF(403) → 미인증(401) →BADGE_MANAGE게이트(403).- 표시: 리뷰 작성자 배지 칩(
listReviewsJSON + game-detail.jsp JS,UserBadgesQueryMapper.listActiveBadgeKeysByUserIds배치쿼리로 N+1 차단) + profile.jsp 본인 배지.
검증
L1 347/347 GREEN(baseline 318→347, BadgeServiceTest 12 + BadgeManageControllerTest 14 + GameReviewControllerTest 회귀 3). W1 권한 카탈로그 회귀 0(런타임 로그 "권한 카탈로그 시드 완료: 4 키", 카운트 가정 테스트 전부 GREEN). L2: 자동부여 멱등 · 회수후 재획득 부분유니크 · reputation dedupe · countActive 집계 · 배치 alias camelCase 인용 · CHECK/FK(throwaway DB). contextLoads 신규 @MockBean 6.
보안 (프로젝트 보안 원칙 추적)
본 작업에서 신설·연결된 인가/검증/방어 게이트. 모두 단위 + L2 DB 계약(+ 보안핵심 2건은 adversarial 코드추적 감사)으로 검증.
| 게이트 | 적용 W | 내용 |
|---|---|---|
| CONTENT_MODERATE 재사용 | W3-1 | 태그 생성/부착 게이트. W1 선언키 재사용(신규키 0). |
| POST_WRITE enforcement | W3-3 | W1 선언키의 첫 enforcement 연결(이전 소비처 0). 게시글 작성 게이트. |
| BADGE_MANAGE 신규 | W4 | 4번째 권한키 신규. 배지 grant/revoke 게이트(/manage/badges). W1 카탈로그 3→4. |
| SsrfSafeFetcher 9항목 | W3-3 | scheme allowlist·사설/루프백 IP 차단·DNS rebinding·redirect 재검증·size/timeout. option-b hostname-connect(restricted-header 우회 제거). |
| 마크다운 sanitize | W3-3 | commonmark 렌더 후 jsoup Safelist sanitize(XSS 차단). adversarial XSS SOUND. |
| zip-slip / 심링크 / zipbomb | W3-5 | ZipSecurity canonical 경계 + 심링크 거부 + 3중상한. adversarial zip-slip SOUND. ATOMIC_MOVE 원자 교체. |
| 방문수 dedupe | W3-1 | game_views interval 기반 재방문 제외(view_count 부풀림 방지). |
| CSRF 전수 | 전 W(상태변경) | 모든 상태 변경 핸들러 CSRF 선검증 → 누락 시 403. |
MyBatis ${} 0건 |
전 W | 신규 매퍼 전수 #{} 바인딩, 동적 치환 0. |
| §33 alias 케이스폴딩 | W3-1/4, W4 | VIEW/집계/배치 매퍼 camelCase alias 큰따옴표 인용 — Postgres 소문자 폴딩 회피. |
검증 결과 요약
최종 컨테이너 ./mvnw -o test(eclipse-temurin:21-jdk + ~/.m2 마운트, 오프라인) 347/347 GREEN(baseline 190 → 누적 +157 신규테스트, 회귀 0).
| 서브 | L1 누적 | L2 dev DB contract(격리 throwaway) | 비고 |
|---|---|---|---|
| W3-1 | 235/235 GREEN | PASS(alias 폴딩 대조·NULLS LAST·AND⊆OR·ILIKE·interval dedupe) | +45 신규, 편차 2(의미보존) |
| W3-4 | 243/243 GREEN | PASS(keyset 혼합방향 OR분해 무중복·무누락·tie-break) | 편차 0 |
| W3-3 | 287/287 GREEN | PASS(board keyset·insertIgnoreDup·FK) | backward 1회(verification+adversarial SSRF 2중검증이 vacuous 포착) |
| W3-5 | 318/318 GREEN | PASS(audit·outcome CHECK·FK·alias) | adversarial zip-slip SOUND |
| W4 | 347/347 GREEN | PASS(멱등·재획득 부분유니크·dedupe·집계·배치 alias·CHECK/FK) | W1 카탈로그 3→4 회귀 0 |
- L2 비파괴 원칙: 모든 L2 contract 는 격리 throwaway postgres:16(생성 → DDL 적용 → 실측 → DROP)로 수행. 사용자 dev(
bibimbap) DB 무접촉(전 W). - 2중 검증 가치: W3-3 에서 verification(테스트 실행)이 통과시킨 vacuous SSRF 를 adversarial 감사(코드추적)가 포착 → option-b 전환으로 실질 차단 확보.
상세는 .atp/work-session/20260629-100849/verification.md + report.md §verified_by_me 참조.
미적용 / 이월 (needs_user_verification)
| 항목 | 사유 |
|---|---|
실 dev DB(bibimbap) DDL 미적용 |
L2 는 throwaway DB 로 통과했으나 실 dev DB 에는 미적용(파괴적 조작 회피). 신규 DDL 5종(tag/games-hub/board/game-upload/badge) + (W2 carryover) jam DDL 3종 = 총 8종 을 db/apply-local-ddl.sh 로 일괄 적용 필요(§6 수동게이트). L3 선행. → [해소됨] 아래 §후속 세션(20260629-142115) 참조. |
| L3 런타임 스모크(WAR 기동 후) | ① 검색 /games/search 태그·정렬·잼필터 + 방문수 dedupe ② 허브 / keyset 더보기 + 진행중 잼 배너 ③ 포스팅 작성(POST_WRITE)/sanitize 렌더/OG 미리보기/유니티 피드 폴링 + ★실 HTTPS 외부 fetch 동작(option-b hostname-connect 실 TLS — 단위는 HTTP만) ④ 업로드 zip-slip/포맷 거부 + 정상 Unity 빌드 + replaceUuid 교체 ⑤ 배지 자동부여(리뷰·업로드)·수동 grant/revoke(BADGE_MANAGE)·리뷰작성자 칩. → [배포 후 과제로 이월] 상세 체크리스트: maintenance/post-deploy-verification-checklist.md. |
| W4 표시 deferral | 리뷰작성자 배지 경로만 구현. 프로필 myBadges 주입·게임카드 creator 배지 미구현(의도된 deferral). 게임카드 SSR 은 WebMvcController hub + SearchController 가 담당(W3-4/타 W 산출물)이라 본 작업 범위 밖. → [해소됨] 아래 §후속 세션(20260629-142115) 참조. |
| W3-3 SSRF 잔여 rebinding TOCTOU | option-b hostname-connect 의 connect 시 재resolve 창(DNS 캐시로 최소화, 위협모델 LOW — URL 제출자=권한자). 하드닝 옵션: networkaddress.cache.ttl 설정 검토. → [best-effort 해소] 아래 §후속 세션(20260629-142115) 참조. |
| W3-5 배포 하드닝 | (1) 업로드 저장루트 static 트리 밖 이전(LOW: static 트리 .tmp 노출 가능) (2) gameRoot 경로 이중중첩 정합(app.upload.game-storage-path=src/main/resources/static/game → resolve('game') → .../static/game/game). 설계 스코프-아웃(canonical 경계는 보장). dev 자산 위치 영향이라 사용자 config 결정 필요. → [해소됨] 아래 §후속 세션(20260629-142115) 참조. |
후속 세션 (20260629-142115) — 배지 표시 배선 완료 + 보안 하드닝 b1/b2
관련 세션: .atp/work-session/20260629-142115/report.md | 구현 소유권: .atp/work-session/20260629-142115/implementation/ | 검증: .atp/work-session/20260629-142115/verification.md
작업 1 — DB DDL 사용자 직접 적용 (no-op 기록)
직전 세션 needs_user_verification "실 dev DB DDL 미적용" 항목에 대해 사용자가 직접 실 dev DB 에 신규 8종(tag/games-hub/board/game-upload/badge DDL 5종 + W2 jam DDL 3종)을 적용 완료한 것을 보고 수신. 이 항목은 해소됨으로 처리. 코드/스키마 변경 없음.
작업 2 — W4 배지 표시면 배선 (3d10449)
| 서브 | 내용 |
|---|---|
WebMvcController |
UserBadgesQueryMapper 주입 → hub 게임카드 creatorBadges model attr 추가 |
SearchController |
UserBadgesQueryMapper 주입 → 검색 결과 게임카드 creatorBadges model attr 추가 |
index.jsp |
게임카드 creator 배지 칩 표시 (hub + 검색 결과 통합) |
GameReviewController |
authorBadges 정본 패턴 이미 존재 — 패턴 답습, 신규 SQL 0 |
| profile.jsp | myBadges 주입 표시 (이전 세션에서 구현 완료 확인, 이번 배선으로 완전 연결) |
- 신규 SQL 0. 기존
UserBadgesQueryMapper.listActiveBadgeKeysByUserIds배치쿼리 재사용(N+1 차단). - L1 352/352 GREEN(baseline 347→352, +5 WebMvcControllerTest/SearchControllerTest 신규). 회귀 0.
- 직전 세션 "W4 표시 deferral" 완전 해소.
작업 4 — 보안 하드닝 b1/b2 (9041bb7)
b1 — SsrfSafeFetcher DNS rebinding TOCTOU 완화
@PostConstruct initDnsCachePolicy()추가:java.security.Security.setProperty("networkaddress.cache.ttl", "30").- best-effort: @PostConstruct 가 첫 InetAddress 조회 이전에 실행되면 유효. 결정적 보장이 필요하면 JVM 레벨(
$JAVA_HOME/conf/security/java.security)이 정본. - 소스 내 주석으로 한계 안내 명시(AC-b1-2 정적 검증 PASS).
- L3 스모크 확인 항목 추가: 앱 기동 후
java.security.Security.getProperty("networkaddress.cache.ttl")=="30"확인 — 미반영 시 JVM 레벨 java.security 설정으로 격상. 상세: maintenance/post-deploy-verification-checklist.md.
b2 — 업로드 저장루트 static 서빙 트리 밖 이전
application.properties:app.upload.game-storage-path=${user.home}/.bibimbap/uploads로 변경.- 효과: 업로드물이 static 서빙 트리 밖에 저장 → 웹서버 직접 노출 차단, 컨트롤러 권한게이트 경유 강제.
- 수동 이전 필요:
src/main/resources/static/profile/8/*(user 8 프로필 이미지)를~/.bibimbap/uploads/profile/8/로 수동 이전 필요. 미이전 시 user 8 프로필 이미지 404.static/game은 비어있어 게임 자산 이전 불필요. - 상세 절차: maintenance/post-deploy-verification-checklist.md §자산 수동 이전.
- L1 353/353 GREEN(baseline 352→353, SsrfSafeFetcherTest.initDnsCachePolicySetsPositiveTtl 신규 1). 회귀 0.
검증 결과
최종 ./mvnw -o test 353/353 GREEN(Tests run: 353, Failures: 0, Errors: 0, Skipped: 0). 전 항목 pass. 상세: .atp/work-session/20260629-142115/verification.md.
이월 (배포 후 과제)
L3 런타임 스모크 전체 + b2 자산 수동 이전 → maintenance/post-deploy-verification-checklist.md 로 이월.