Merge PR #1 (docs/project-analysis): ATP 문서 체계 + 프로젝트 종합 분석

origin/main 의 사고 커밋(f6f691c)이 csrf 보안 수정과 PR 문서의 부분 스냅샷을
한 커밋에 섞어 PR 이 mergeable:false 가 된 상태를 해소.

충돌 해결:
- csrf 보안 수정(UserController/login/signup/테스트 2건)은 main 버전 유지
- 충돌 문서(project-analysis.md, docs/index.md, .serena/project.yml)는 PR 정식본 채택
- CLAUDE.md 는 main 프로젝트 지침 + PR ATP 블록 결합
- docs/security/security-remediation-checklist.md(고유 내용) 유지
- 사고 커밋이 남긴 README.md 5건 제거 (PR 의 index.md 컨벤션으로 통일)
- .atp/work-session 파일/디렉토리 타입 충돌 → PR 디렉토리 채택

병합 결과 src/ 트리는 origin/main 과 바이트 동일 (코드 변경 0, csrf 보존).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
이정수 2026-06-17 10:00:39 +09:00
commit 8c41b228c9
42 changed files with 1313 additions and 321 deletions

View File

@ -1,47 +0,0 @@
# ATP Work Session: 2026-06-16 Project Analysis
## Objective
Introduce a docs/ workflow scaffold and record a read-only full-project analysis focused on architecture, security, quality, and domain behavior.
## Constraints
- Do not modify `src/`.
- Do not modify `pom.xml`.
- Documentation and project guidance files only.
## Files Added
- `docs/index.md`
- `docs/analysis/README.md`
- `docs/analysis/2026-06-16-project-analysis.md`
- `docs/security/README.md`
- `docs/security/security-remediation-checklist.md`
- `docs/architecture/README.md`
- `docs/development/README.md`
- `docs/adr/README.md`
- `CLAUDE.md`
- `.atp/work-session`
- `.serena/project.yml`
## Files Updated
- `docs/db-update-query-generator.md`
- `docs/user-signup-schema.md`
## Findings
- SQL injection surface was not found in scanned mapper/controller code because MyBatis mapper SQL uses `#{}` binding and no `${}` dynamic replacement was found.
- Password storage uses PBKDF2-SHA256 with 210,000 iterations.
- Most state-changing endpoints use `CsrfTokens.isValid`.
- `POST /login` and `POST /signup` do not validate CSRF and are the primary MED security gap.
- Prototype leftovers include `abstracts`, `header.jspf`, and empty `GameCatalog`.
- Likes/comments have mapper/schema traces but the game detail UI persists them only in `localStorage`.
- Tests are currently insufficient for security regressions.
## Next Work
- B1: Add CSRF validation to login/signup.
- B2: Remove or document prototype dead code.
- B3: Connect likes/comments to server persistence after policy decisions.
- B4: Pin Spring Boot release version, harden session cookies, and add dependency CVE scanning.

View File

@ -0,0 +1,40 @@
---
phase: documentation
agent: documentation-advisor
agent_version: 1
generated_at: 2026-06-16T11:17:11Z
concerns:
- "프로젝트 docs/development/documentation-guidelines.md 는 소비 프로젝트에 복사되지 않음(번들 캐시 참조). atp 플러그인 캐시(2.1.0) 의 권위 가이드를 직접 읽어 analysis frontmatter 스키마(kind/perspective/valid_starting_point_for/superseded_note)를 따랐다."
- "기존 docs/analysis/ 에 선행 문서가 없어 매칭할 명명 패턴 부재. 루트 docs 의 서술형 kebab-case 관례 + analysis 스냅샷 날짜접두 관례를 조합해 2026-06-16-project-analysis.md 로 명명했다."
- "원본 analysis.md 의 추정/미확인 마커(세션쿠키 Secure/SameSite, SNAPSHOT 의도, 소셜 확장, CVE 미스캔)를 사실로 격상하지 않고 그대로 보존했다."
concerns_checked: true
---
# 문서화 보고
## 작성/수정된 문서
| 경로 | 카테고리 | 유형 | 링크 추가한 index | 교차 링크 |
|---|---|---|---|---|
| docs/analysis/2026-06-16-project-analysis.md | analysis | 신규(정본 분석) | docs/analysis/index.md (목록 1줄 추가) | 아웃바운드 → db-update-query-generator.md, user-signup-schema.md, development/document-category-classification.md / 인바운드 ← 동일 2개 문서의 "관련 문서" |
| docs/analysis/index.md | analysis | 수정(목록) | (자기 자신) | — |
| docs/db-update-query-generator.md | (루트) | 수정(관련 문서 링크) | — | → 신규 분석 인바운드 |
| docs/user-signup-schema.md | (루트) | 수정(관련 문서 링크) | — | → 신규 분석 인바운드 |
## 의사결정 기록 위치
- 정본 분석: `docs/analysis/2026-06-16-project-analysis.md` — D1~D4 + 주요 발견 Top10 + 종합 판단 + open_questions 전부 보존. 모든 file:line 인용 유지, 추정/미확인 마커 보존.
- 출처(원본): `.atp/work-session/20260616-111711/research/analysis.md`
- frontmatter 적용 필드: `kind, title, description, perspective(neutral), valid_starting_point_for, owner, stability(snapshot), last_reviewed, source, superseded_note`
## 따른 규약
- 읽은 가이드: `docs/index.md`, `docs/analysis/index.md`, `docs/development/index.md`, `docs/development/document-category-classification.md`, 그리고 atp 플러그인 캐시의 권위 `documentation-guidelines.md`(2.1.0).
- 카테고리 판별: 코드/흐름/리스크 분석(수정 없음) → `analysis/` (분류 기준 §6, §빠른결정순서 6번).
- 링크 강제 규칙: 신규 문서 → 카테고리 index.md 1줄 링크 추가 완료 + 기존 관련 문서 2건에 인바운드 교차 링크 추가 완료.
## 추후 문서화가 필요한 항목
- 보안 미결 결정(login/signup CSRF, 세션쿠키 Secure/SameSite, spring-boot SNAPSHOT 고정)이 design 단계에서 확정되면 → `adr/`(되돌리기 어려운 결정) 또는 `security/`(기준 문서)로 분리 기록 필요.
- game_comments/game_likes 서버 영속화의 "의도된 미완성 vs 폐기" 판정이 내려지면 → `backlog/`(보류) 또는 `changes/`(구현 시) 기록.
- D2 보안 발견을 독립 `security/` 기준 문서로 승격할지 검토(현재는 analysis 한 문서에 통합).

View File

@ -0,0 +1,97 @@
---
schema_version: 1
sid: 20260616-111711
started_at: 2026-06-16T11:17:11+09:00
ended_at: null
user_request: "프로젝트 분석"
resumed_from: null
---
# Report — 프로젝트 분석
## Summary
bibimbap = Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰 웹앱(게임 공유 + 팀원 모집). 4축 read-only 종합 분석 완료.
- 보안 기초 견고(SQLi 0, PBKDF2 210k, CSRF 더블서밋, zip-slip/XSS 방어 일관)
- 부채는 프로토타입 화석 코드 + 서비스 레이어 부재 + 테스트 전무에 집중
- research-advisor 2회 디스패치(첫 호출 async worker await 중 TaskOutput 조기판독 → 재디스패치). 두 산출 상호검증되어 결과 신뢰도↑. 산출물: research/analysis.md (198줄)
## Orientation (사전 파악)
- Stack: Spring MVC + MyBatis, Java/Maven (pom.xml, .mvn)
- Package: com.pandoli365.bibimbap — Java 30 files
- Layers: abstracts / config / controller(+api) / data / game / mapper / security
- View: JSP (src/main/webapp/WEB-INF/jsp, views), static resources
- Config: src/main/resources/{dev,live}/db.properties.example, application.properties
- Domain: 팀원 모집 + 프로젝트 목록 웹앱 (recent commits)
- graph: no-graph (docs/graph/index.md scopes 비어있음)
- docs/: 대부분 ATP/graphify 템플릿 스캐폴딩 (owner: template-maintainer) — 프로젝트별 architecture 문서 없음
## Invocations
- inv-001:
advisor: research-advisor
status: completed (interim-misread, see notes)
model_choice: { phase: analyze, dispatch_size: parallel, tier: large, effort: high, resolved_model: inherit(opus), capped: false }
output: research/analysis.md (171줄, 4 parallel-explorer)
notes: "async worker await 중 orchestrator 가 TaskOutput 을 조기 판독해 standby 메시지를 종료로 오인 → inv-002 재디스패치 유발. 실제로는 정상 완료."
- inv-002:
advisor: research-advisor
status: completed
model_choice: { phase: analyze, dispatch_size: l-batch, tier: large, effort: high, resolved_model: inherit(opus), capped: false }
output: research/analysis.md (198줄, 직접 탐색, 디스크 잔존본)
notes: "no-async 규칙 명시 재디스패치. inv-001 과 핵심 발견 전부 일치(교차검증)."
# Advisor Invocation Decision Log
# 각 advisor 호출/스킵 판단 즉시 1줄 append
- advisor: graphify-lookup-advisor
decision: skip
rationale: 'graph/index.md scopes 비어있음(no-graph) 확인 — 조회 대상 없음, 직접 src 탐색으로 대체'
checked_at: 2026-06-16T11:17:11+09:00
- advisor: requirements-advisor
decision: skip
rationale: '분석 축 AskUserQuestion 으로 직접 확정 — 4축 전부 선택(아키텍처/보안/품질/도메인). 마이크로 요구해석, advisor 불필요'
checked_at: 2026-06-16T11:18:00+09:00
- advisor: research-advisor
decision: call
rationale: '4축 종합 분석 = 탐색적 다파일 교차 추론. Tier-3 로 parallel-explorer 4 fan-out. tier=large(opus, self-cap 내), effort=high'
checked_at: 2026-06-16T11:18:00+09:00
- advisor: documentation-advisor
decision: call
rationale: '사용자 A 선택 — analysis.md 를 docs/analysis/ 영구 문서로 보존 + index 링크. docs-sync phase, tier=medium'
checked_at: 2026-06-16T11:33:00+09:00
## Decisions
- 본 세션은 코드 변경 0 (읽기 전용 분석). design/implementation/verification advisor 비적용.
## user_signals
positive: []
negative: []
## graph_refresh
skip: no-scope-change — 코드 변경 0(read-only 분석). graph-refresh-checker 미호출. (no-graph 상태이나 분석 세션이라 생성 유예; 필요 시 후속 `/graphify src` 권장)
## verified_by_me
- L1: skip — 코드 변경 0 (read-only 분석, typecheck/test 대상 없음)
- L2: skip — 외부 의존 변경 없음
- 산출물 무결성: research/analysis.md 디스크 존재 확인(198줄), 모든 사실 항목 file:line 근거 + 불확실 항목 마커(추정/미확인) 보존 확인. 2개 독립 산출 교차검증 일치.
## needs_user_verification
- 분석 자체는 검증 불요. 단 analysis.md open_questions 1~5 는 코드 밖 의도/배포설정 의존 → 사용자 답변 필요(특히 likes/comments 영속화 의도, 세션쿠키 Secure/SameSite, SNAPSHOT 고정 사유).
## open_items
- .atp/work-session/20260616-111711/ (report.md, research/analysis.md, documentation.md) — 미커밋.
- A 완료: docs/analysis/2026-06-16-project-analysis.md(신규 218줄) + docs/analysis/index.md(링크) + docs/db-update-query-generator.md·docs/user-signup-schema.md(교차링크) — 미커밋. 커밋은 사용자 지시 대기(base 규칙: 커밋은 요청 시에만).
- src/·pom.xml 무변경 확인(read-only 분석 준수).
## Invocations (추가)
- inv-003:
advisor: documentation-advisor
status: completed
model_choice: { phase: docs, dispatch_size: s-batch, tier: medium, effort: low, resolved_model: inherit(opus), capped: false }
output: docs/analysis/2026-06-16-project-analysis.md + index + 교차링크 2건; work-session documentation.md
notes: "분석 영구 보존(A). 산출 디스크 존재로 완료 판정(inv-001 교훈 적용)."
## retrospective (orchestrator 직접 — read-only 세션, advisor 미호출)
- skip: retrospective-advisor — 코드 변경 0 + 사용자 negative 시그널 없음. 단 1개 프로세스 교훈 직접 기록:
- LESSON(b-pattern 후보): async 서브에이전트(worker fan-out)는 부모가 worker await 중 "rest" 상태로 보고될 수 있음. TaskOutput status=completed + thin standby 메시지면 종료 단정 말고 산출물 디스크 존재로 완료 판정할 것. 본 세션서 이 오판으로 inv-002 중복 디스패치(~66k 토큰 낭비).
- memory_candidate: { type: feedback, signal_source: process, text: "research-advisor 등 worker-fanout advisor 의 완료 판정은 반환 메시지가 아닌 산출 파일 존재로 한다", docs_sync_target: null }
ended_at: 2026-06-16T11:32:00+09:00

View File

@ -0,0 +1,198 @@
---
phase: research
agent: research-advisor
agent_version: 2
generated_at: 2026-06-16T11:17:11Z
concerns:
- "abstracts 패키지(Service/Request/Result/ErrorResult)는 전부 dead code이며 세션키('id' vs 'userId')가 활성 코드와 불일치 — 향후 재사용 시 인증 우회 함정. requirements 전제(레이어 구조)와 충돌 가능: 실제 서비스 레이어는 존재하지 않음(컨트롤러가 매퍼 직접 호출)."
- "POST /login, POST /signup 에 CSRF 검증 부재(다른 모든 mutation 엔드포인트는 검증함) — login CSRF 가능. design 단계로 넘기기 전 보안 결정 필요."
concerns_checked: true
source_confidence: high
workers_spawned: 0
---
# 조사 결과 — bibimbap 종합 분석 (D1~D4)
> 분석 방식: 30개 Java 파일 + 16개 JSP + 6개 MyBatis 매퍼 전수 직접 읽기(Read/Grep/Bash). 외부 자료 미사용 → 모든 사실은 코드 직접 확인(`확인됨`). 추정 항목은 명시.
## 주제
Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read-only 분석: 아키텍처(D1), 보안(D2, 최우선), 코드품질/기술부채(D3), 도메인/기능(D4).
---
## 주요 발견 Top 10
| # | 심각도 | 발견 | 위치 |
|---|--------|------|------|
| 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 태그(`<sec:authorize>`, `${_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 |
| 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 |
| 10 | INFO | **XSS 방어 일관 적용(비관습적)** — JSTL `<c:out>` 0건이나, 모든 JSP가 Java scriptlet 변수 할당 시점에 `HtmlUtils.htmlEscape()` 적용 후 출력. 사용자 콘텐츠 sink(게임명/제작자/모집글/닉네임/이메일/창작노트/검색어) 전수 확인 결과 escape 누락 없음. CSP·X-Content-Type-Options 도 WebGL asset 응답에 설정. | views/*.jsp 전체, GameAssetController.java:60-66 |
---
## D1 — 아키텍처/구조
### 패키지별 책임 (1줄)
| 패키지 | 역할 |
|--------|------|
| (root) | `BibimbapApplication`(@SpringBootApplication main), `ServletInitializer`(WAR 배포용 SpringBootServletInitializer) |
| abstracts | **dead code** — 미사용 제네릭 Service/Request/Result/ErrorResult 베이스 (확인됨: 참조 0) |
| 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(빈 배열)**, 레거시 정적 게임 카탈로그 폴백(현재 무력) |
| mapper | MyBatis `@Mapper` 인터페이스 6종 — 어노테이션 기반 SQL(XML 매퍼 0) |
| security | `CsrfTokens` — 세션 기반 CSRF 토큰 발급/검증 유틸(static) |
### 요청 흐름
- **페이지 렌더**: Controller → (서비스 레이어 없음) → Mapper → DB → ModelAndView/Model → `/WEB-INF/views/*.jsp` (ViewResolver prefix/suffix는 application.properties).
- **API mutation**: `@PostMapping`/`@DeleteMapping` → CSRF 검증 → 세션 인증 → 입력 검증 → Mapper → JSON ResponseEntity.
- **서비스 레이어 부재**(확인됨): 컨트롤러가 매퍼를 직접 주입·호출. `@Transactional`은 컨트롤러 메서드에 부착. → requirements가 "service 레이어 존재"를 전제했다면 충돌(concern 기재).
- **JSP 렌더 방식**(확인됨): JSTL 미사용. Java scriptlet(`<%%>`/`<%=%>`)으로 `request.getAttribute()` 직접 캐스팅 + `HtmlUtils.htmlEscape()`.
### 레이어별 핵심 클래스
| 레이어 | 클래스 | 역할(1줄) |
|--------|--------|----------|
| 진입 | BibimbapApplication / ServletInitializer | main 부트 / WAR 배포 부트 |
| 페이지 | WebMvcController | `/`,`/{pageName}`(화이트리스트), `/error`, 프로필·게임등록 뷰 게이팅 |
| 페이지+API | RecruitController | `/recruit`(목록), `/recruit/new`(폼+생성 POST), `/recruit/{id}`(상세) |
| API | GameController | 게임 생성/수정/삭제(soft) + 상세/편집 뷰 + 소유권 인가 |
| API | GameUploadController | WebGL zip 업로드(zip-slip 방어), 썸네일·일반파일 업로드 |
| API | GameAssetController | `/game/{uuid}/**` WebGL 정적 서빙 + CSP/인코딩 헤더 |
| API | UserController | signup/login/logout/프로필(닉네임·아바타) + PBKDF2 해싱 |
| API | ApiExceptionControllerAdvice | 업로드 초과·일반 예외 → JSON 에러(스택 비노출) |
| 매퍼 | Games/RecruitPosts/Users/UserAuthIdentities/GameComments/GameLikes Mapper | 어노테이션 SQL CRUD |
### Spring/MyBatis 배선·프로파일
- `@SpringBootApplication` 단일. MyBatis는 `mybatis-spring-boot-starter` 자동 배선 + `@Mapper` 스캔(별도 @MapperScan 불필요, 확인됨).
- 프로파일: pom `dev`/`live` 프로파일이 `app.profile` 치환 → `application.properties``spring.profiles.active=@app.profile@`(빌드 필터링) → `spring.config.import``${profile}/db.properties` 로드. dev/live는 PostgreSQL 동일 호스트·다른 schema(`currentSchema=dev|live`).
- DB props: `db.properties.example`만 git 추적, 실제 `db.properties`는 .gitignore 처리(확인됨: gitignore `### Local secrets ###`).
- 앱 진입: embedded Tomcat(`spring-boot-starter-web`) + WAR(`tomcat-embed-jasper` for JSP, `spring-boot-starter-tomcat` provided). web.xml 없음(Servlet 3+ initializer).
---
## D2 — 보안 (최우선)
### 인증/인가
- **메커니즘**(확인됨): Spring Security 미사용(의존성 없음). `HttpSession` 속성 기반 커스텀. login 시 `request.changeSessionId()`로 세션 고정 공격 방어(UserController.java:152). remember 옵션으로 세션 타임아웃 30분/30일 분기.
- **인가**(확인됨): 리소스 mutation마다 `userId.equals(resource.getUserId())` 소유권 체크 일관 적용(GameController updateGame:183, deleteGame:239, editView:142). role 기반 인가는 미구현(role 컬럼은 저장만, 'USER' 고정).
### 자격증명/시크릿
- 하드코딩 시크릿 **없음**(확인됨): tracked 파일은 `*.example`(placeholder `your_username/your_password`)뿐. 실제 db.properties는 gitignore. application.properties에 비밀값 없음.
- 비밀번호 저장: PBKDF2-SHA256 210k iter(상기 Top#2). 평문·약한해시 없음.
### SQL 인젝션 (전수 — `${` vs `#{`)
- **전 매퍼 `${}` 보간 0건**(확인됨, grep 전수). 모든 동적값은 `#{}` 또는 `@Param`+`#{}`. 검색: `searchVisibleGames``ILIKE CONCAT('%', #{query}, '%')`로 안전(GamesMapper.java:85-87).
- 단, **테스트 코드** `DbUpdateQueryGeneratorTest``String.formatted()`로 schema명을 SQL에 직접 삽입(line 333,369) + `quoteIdentifier`/`escapeSql`로 수동 이스케이프. 이는 프로덕션 경로 아님(테스트 전용, surefire에서 제외됨)이나 패턴상 주의(LOW, 입력이 상수 'dev'/'live'라 실위험 없음 — `확인됨`).
### JSP XSS / CSRF
- **XSS**(확인됨): `<c:out>` 0건이나 모든 사용자 콘텐츠 sink가 `HtmlUtils.htmlEscape()` 처리(index.jsp:506-507, recruit-detail.jsp:전 필드, recruit-list.jsp:286-292, profile.jsp:16-18, header.jsp:11, game-detail.jsp:7-21, game-register.jsp:13-17, theme-init.jsp:7). raw `<%=` sink들도 추적 결과 모두 escape된 변수 또는 안전값(ctx, id, String.format)임을 확인.
- 게임 댓글: 클라이언트 JS가 `textContent`로 렌더(game-detail.jsp:970,983) → DOM XSS 안전. 단 서버 미저장.
- **CSRF**(확인됨): 커스텀 더블서밋 — `theme-init.jsp``<meta name="csrf-token">` + `window.BibimbapCsrf.headers()``X-CSRF-Token` 헤더 주입. 검증은 `CsrfTokens.isValid()`(헤더 또는 `_csrf` 파라미터, 세션값과 `.equals` 비교). **단 login/signup 미적용**(Top#3, MED).
- 세션/쿠키: 기본 JSESSIONID. `Secure`/`SameSite` 명시 설정 없음(application.properties 미존재 → 컨테이너 기본값. 프로덕션 HTTPS에서 Secure 미설정이면 LOW~MED 위험 — **추정**: 배포 톰캣 설정 미확인이라 코드만으로 단정 불가).
### 파일 업로드 보안 (양호)
- zip-slip 방어: `target.startsWith(targetDir)` 정규화 검증(GameUploadController.java:128,200,266-269).
- zip bomb 방어: entry 수 8000 / 압축해제 512MB 상한(line 40-41,262,297).
- path traversal: 프로필 아바타·에셋 서빙 모두 `normalize()`+`startsWith(root)` 검증(UserController.java:248-263, GameAssetController.java:101-104). UUID 검증으로 게임 디렉토리 화이트리스트.
- 콘텐츠 타입: 확장자 화이트리스트(png/jpg/webp/gif). **추정 한계**: MIME/확장자만 검사, 매직바이트 미검증 → polyglot 업로드 가능성(LOW, WebGL asset은 nosniff+CSP로 완화).
### 정정 (orientation 대비)
- orientation은 "JSP under WEB-INF/jsp(+fragments)"를 활성 뷰처럼 기재했으나, 실제 활성 뷰는 `WEB-INF/views/*.jsp`이고 `WEB-INF/jsp/fragments/header.jspf`**orphan dead 잔재**(Top#5)임을 확인.
---
## D3 — 코드 품질/기술부채
| 항목 | 발견 | 위치 |
|------|------|------|
| Dead code | abstracts 패키지 4클래스 전부 미사용; GameCatalog 빈 stub+도달불가 분기; header.jspf orphan | Top#4,5,6 |
| 미완성 기능 | game_comments 스키마·매퍼·DTO 존재하나 컨트롤러 미연결(localStorage 임시) | Top#7 |
| 레이어 위반 | 서비스 레이어 부재 → 컨트롤러가 비즈니스 로직+매퍼 직접 호출(fat controller). UserController 629라인(해싱·파일IO·세션관리·검증 혼재) | UserController.java 전체 |
| 중복 | `sessionUserId()` 동일 메서드가 4개 컨트롤러에 복붙(RecruitController:155, GameController:291, GameUploadController:222, UserController:325, WebMvcController:118). `trimToNull/trimToEmpty`, `imageExtension/profileImageExtension`, UUID 정규화도 중복 | 다수 |
| 미사용 의존성 | Lombok이 pom+컴파일러 플러그인에 선언되었으나 data 클래스는 수동 getter/setter(Lombok 미활용) | pom.xml:74-79, data/*.java |
| 오타/네이밍 | 뷰 파일명 `errer.jsp`(error 오타), 메서드명 `ChackService`(Check 오타), 필드 `is_login`(자바 네이밍 컨벤션 위반 snake_case) | views/errer.jsp, abstracts/Service.java:7,11 |
| 미사용 코드 | `WebMvcController.isMobileDevice()` 정의만 되고 호출 없음(line 130) | WebMvcController.java:130 |
| 에러 처리 | API는 일관(ResponseEntity+JSON, 스택 비노출). 단 `Result` 생성자의 `System.out.println("잘못된 status")`(line 21)는 로깅이 아닌 stdout | abstracts/Result.java:21 |
| 매직값 | 역할/참여유형/상태 한글 문자열 하드코딩 Set(RecruitController:25-27); status 1000("NULL USERS") 같은 비표준 코드 | RecruitController.java:25-27 |
| 설정 위험 | 업로드 1GB 상한(application.properties), `max-swallow-size=-1`(무제한) → DoS 표면(**추정**: 인증 게이트 뒤라 완화되나 큰 값) | application.properties |
### 테스트
- `BibimbapApplicationTests.contextLoads()` — 스모크만(빈 본문).
- `DbUpdateQueryGeneratorTest` — 실제 단위테스트 아님. dev/live DB 직접 연결해 schema/data diff SQL을 `src/test/db/dev-to-live-update.sql`로 출력하는 **운영 도구**(surefire에서 제외, pom 명시). 실DB 필요 → CI 불가.
- **커버리지 갭**: 컨트롤러·매퍼·인증·업로드·CSRF 로직 단위/통합 테스트 전무. 보안 핵심(PBKDF2 검증, zip-slip, 소유권 인가) 미검증.
### pom 위생
- **spring-boot 3.5.14-SNAPSHOT**(확인됨) — SNAPSHOT은 비재현·비프로덕션. spring-snapshots 저장소 의존. 안정 릴리스로 고정 필요(MED 부채, **추정**: 보안패치 추적 어려움).
- Java 21, MyBatis starter 3.0.5, PostgreSQL(runtime), 의존성 수 적고 깔끔. CVE 직접 스캔 미수행(외부 조회 안 함 — `미확인`).
---
## D4 — 도메인/기능
### 기능 인벤토리 (기능 → 엔드포인트 / 매퍼 / 뷰 / 데이터)
| 기능 | 엔드포인트 | 매퍼 | 뷰 | 데이터/테이블 |
|------|-----------|------|-----|--------------|
| 게임 목록/검색 | GET `/`(q 검색) | GamesMapper.getVisibleGames/searchVisibleGames | index.jsp | games, users |
| 게임 상세 | GET `/game/{id}` | getGame | game-detail.jsp | games, users |
| 게임 등록 | GET `/game/new`(뷰), POST `/game/new` | addGame, nextSortOrder | game-register.jsp | games |
| 게임 수정/삭제 | GET/POST `/game/{id}/edit`, DELETE `/game/{id}` | updateGame, softDeleteGame, softDeleteGameComments, deleteGameLikes | game-register.jsp(edit) | games, game_comments, game_likes |
| WebGL 업로드 | POST `/api/game-files`, `/webgl-zip`, `/thumbnail`, GET `/ping` | — (파일시스템) | (AJAX) | 업로드 디렉토리 |
| WebGL 서빙 | GET `/game/{uuid}/**` | — | (iframe) | 파일시스템 |
| 팀원 모집 목록/상세 | GET `/recruit`, `/recruit/{id}` | getVisibleRecruitPosts, getRecruitPost | recruit-list.jsp, recruit-detail.jsp | recruit_posts, users |
| 모집글 작성 | GET/POST `/recruit/new` | addRecruitPost, nextSortOrder | recruit-form.jsp | recruit_posts |
| 회원가입/로그인/로그아웃 | POST `/signup`,`/login`,`/logout` | Users/UserAuthIdentities add/get/update | signup.jsp, login.jsp | users, user_auth_identities |
| 프로필 | GET `/profile`(뷰), POST `/profile/nickname`,`/profile/avatar` | getUser, updateUser, getGamesByUserId | profile.jsp | users, user_auth_identities |
| 정적 페이지 | GET `/terms`,`/operation-policy` | — | terms.jsp, operation-policy.jsp | — |
| 게임 댓글 | **(엔드포인트 없음)** | GameCommentsMapper(미연결) | game-detail.jsp(localStorage) | game_comments(미사용) |
| 게임 좋아요 | **(엔드포인트 없음)** | GameLikesMapper(deleteGameLikes만 호출) | game-detail.jsp(JS baseLikes) | game_likes(쓰기경로 없음) |
### 데이터 모델 (매퍼 SQL에서 역추출 — `확인됨`)
- **users**(id, display_name, canonical_email, avatar_url, role, status, last_login_at, created_at, updated_at, is_delete)
- **user_auth_identities**(id, user_id, provider, provider_user_id, email, password_hash, display_name, avatar_url, last_login_at, created_at, updated_at, is_delete) — provider='email' 고정, 소셜 확장 의도된 스키마(provider/provider_user_id)이나 email만 구현(**추정**)
- **games**(id, user_id, name, creator_note, git_url, webgl_path, thumbnail_url, like_count, is_visible, sort_order, created_at, updated_at, is_delete)
- **recruit_posts**(id, user_id, project_name, genre, summary, role, project_status, participation_type, expected_period, team_members, contact, description, reference_url, deadline_at, is_visible, sort_order, created_at, updated_at, is_delete)
- **game_comments**(id, game_id, nickname, content, created_at, deleted_at, is_delete) — 매퍼는 완성, 미연결
- **game_likes**(id, game_id, user_key, created_at)
- 공통 패턴: `is_delete` soft-delete + `is_visible` 노출제어 + `sort_order` 정렬. 모든 조회가 `is_delete IS NOT TRUE` 필터(확인됨, 일관).
### 사용자 플로우
1. 비로그인: 홈에서 게임 탐색/검색 → 게임 상세(WebGL 플레이, 댓글은 로컬), 모집 목록/상세 열람.
2. 가입(이메일+비번 8자+약관) → 로그인(세션, remember) → 프로필(닉네임/아바타 변경).
3. 로그인 사용자: 게임 등록(zip 업로드→썸네일→메타 저장), 본인 게임 수정/삭제, 모집글 작성.
4. 인가: 게임 수정/삭제는 작성자만(소유권 체크).
---
## 종합 판단 (상위 패턴 · 충돌 · 갭)
**강점**: 보안 기초가 의외로 탄탄하다 — SQLi 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관, 세션 고정 방어, 소유권 인가 일관. 코드가 "보안을 의식하고 작성"된 흔적이 뚜렷.
**핵심 부채 패턴**: 프로토타입 잔재가 곳곳에 화석으로 남음 — (a)abstracts 미사용 프레임워크 시도, (b)GameCatalog 정적 카탈로그→DB 마이그레이션 후 stub만 잔존, (c)Spring Security 기반 header.jspf orphan, (d)game_comments/game_likes 서버 기능 미완성(스키마만). 이들은 "구현되다 만/대체된" 코드로, 신규 개발자에게 혼란·인증함정(abstracts의 'id' 키)을 유발.
**아키텍처 갭**: 서비스 레이어 부재로 fat controller(특히 UserController 629줄). 중복 헬퍼(sessionUserId 5중복) → 공통 유틸/베이스 추출 여지. 테스트 거의 전무가 가장 큰 리스크(보안 로직 회귀 무방비).
**보안 미결 결정**: login/signup CSRF 적용 여부, 세션쿠키 Secure/SameSite, spring-boot SNAPSHOT 고정 — design 단계 보안 결정 필요.
**requirements 충돌 가능**: "레이어드 아키텍처(controller/service/mapper)" 전제가 있었다면, 실제로 service 레이어는 없음(abstracts는 dead). → concern 기재.
---
## 미해결 / open_questions
1. 게임 좋아요·댓글의 서버 영속화는 의도된 미완성인가, 폐기된 기능인가? (스키마·매퍼는 완비, 엔드포인트만 없음) — 코드만으로 의도 판별 불가(`미확인`).
2. 세션 쿠키 Secure/SameSite·HTTPS 강제는 배포 톰캣/리버스프록시 설정에 의존 — 저장소 코드 밖이라 `미확인`. 프로덕션 설정 확인 필요.
3. `provider`/`provider_user_id` 컬럼 = 소셜로그인 확장 예정 스키마인지(현재 email 전용) — `추정`.
4. spring-boot 3.5.14-SNAPSHOT을 의도적으로 SNAPSHOT 유지하는 이유(특정 미릴리스 픽스 의존?) — `미확인`.
5. 의존성 CVE 스캔 미수행(외부 조회 안 함) — 별도 `mvn dependency-check` 또는 OSV 조회 권장.

View File

@ -0,0 +1,127 @@
---
schema_version: 2
session_id: 20260616-123205
resumed_from: null
started_at: 2026-06-16T12:32:05+09:00
ended_at: 2026-06-16T13:03:00+09:00
user_request: |
PR 생성까지했었는데 생각해보니 세레나 설정 및 그래피파이 생성을 안했더라고.
→ Serena onboarding(memories 생성) + graphify 지식그래프 생성 두 가지 미수행분 보강.
---
# Summary
PR(docs/project-analysis 브랜치) 까지 진행된 상태에서 누락된 두 운영 작업 보강:
(1) Serena onboarding — `.serena/memories/` 비어있음. project.yml(languages: java) config 자체는 존재.
(2) Graphify 생성 — `docs/graph/index.md` 가 placeholder(scopes: [], last_generated_at: null), 실제 산출물 없음.
코드 로직 변경 0건의 artifact 생성 작업이므로 advisor 파이프라인 없이 orchestrator 직접 수행(§1 meta/tool-exec 예외).
# Advisor Invocation Decision Log
- advisor: requirements-advisor
decision: skip
rationale: '요청 명확 — 누락분(serena onboarding + graphify) 보강. 스코프 결정 1건만 사용자 확인.'
checked_at: 2026-06-16T12:32:05+09:00
- advisor: research-advisor / graphify-lookup-advisor
decision: skip
rationale: '외부/그래프 조회 불필요. 현재 코드베이스 자체가 입력.'
checked_at: 2026-06-16T12:32:05+09:00
- advisor: design-advisor / implementation-advisor
decision: skip
rationale: '코드 설계/구현 아님 — serena tool + /graphify skill 직접 실행. artifact 생성.'
checked_at: 2026-06-16T12:32:05+09:00
- advisor: verification-advisor
decision: skip
rationale: '코드 변경 0줄(§9 의무 트리거 미해당). 대신 생성 산출물 존재/메타 정합성 직접 확인.'
checked_at: 2026-06-16T12:32:05+09:00
# Invocations
- id: inv-001
layer: orchestrator
name: orchestrator
started_at: 2026-06-16T12:32:05+09:00
input_digest: 'serena/graph 상태 파악 + 프로토콜 로드'
output_digest: 'serena onboarding 미수행 + graphify placeholder 확인 + Java LSP SSL 실패 진단·fix'
- id: inv-002
layer: orchestrator
name: orchestrator
input_digest: 'Serena onboarding — 코드베이스 LSP/Read 탐색'
output_digest: 'memory 5종 작성(core/tech_stack/suggested_commands/conventions/task_completion)'
- id: inv-003
layer: worker
name: graphify-semantic-subagent (src chunk1)
parent_invocation_id: inv-002
input_digest: 'src 코드 16파일 semantic 추출'
output_digest: '16 nodes, 19 edges, 3 hyperedges'
model_choice: { phase: graphify-exec, dispatch_size: s-batch, tier: medium, resolved_model: inherit, rationale: 'AST 보완 semantic 엣지 추출 — 기계적, medium' }
- id: inv-004
layer: worker
name: graphify-semantic-subagent (src chunk2)
parent_invocation_id: inv-002
input_digest: 'src 코드 15파일 semantic 추출'
output_digest: '15 nodes, 29 edges, 3 hyperedges'
model_choice: { phase: graphify-exec, dispatch_size: s-batch, tier: medium, resolved_model: inherit, rationale: 'src chunk2' }
- id: inv-005
layer: worker
name: graphify-semantic-subagent (docs)
parent_invocation_id: inv-002
input_digest: 'docs 23파일(md21+sql2) 개념/인용/rationale 추출'
output_digest: '44 nodes, 59 edges, 3 hyperedges'
model_choice: { phase: graphify-exec, dispatch_size: s-batch, tier: medium, resolved_model: inherit, rationale: 'docs 단일 청크' }
# Decisions
- by: orchestrator
at: 2026-06-16T12:34:00+09:00
decision: 'graphify scope = src + docs (사용자 선택)'
rationale: '코드 그래프 + 문서 지식그래프 양쪽 매핑 요청'
- by: user
at: 2026-06-16T12:40:00+09:00
decision: 'Serena Java LSP archive 추출 실패 발견 → 복구 안내 후 세션 중단(halt). memories/graphify 는 LSP 복구 후 재개.'
rationale: 'LSP 깨진 채로 진행하면 향후 코딩 세션의 심볼 지능이 무력. 전역 캐시 재설치는 MCP 재시작 필요 → 사용자 수동 수행.'
# Conflicts
# Open Items
- (없음) — 두 작업 모두 완료.
# verified_by_me
- Serena LSP fix: CA 번들로 실패 URL TLS 검증 status 200 OK. 사용자 재시작 후 LSP probe(get_symbols_overview) "Error extracting archive" 소멸 → Java LS 기동 확인.
- Serena onboarding: `list_memories` 6종(memory_maintenance + core/tech_stack/suggested_commands/conventions/task_completion) 확인, 디스크 파일 존재.
- graphify src: `docs/graph/src/{graph.html,graph.json,audit.md}` 생성 (384 nodes/613 edges/23 communities). 토큰 벤치 28.6x.
- graphify docs: `docs/graph/docs/{graph.html,graph.json,audit.md}` 생성 (44 nodes/56 edges/7 communities).
- index.md 메타(frontmatter scopes/source_commit/last_generated_at + Scopes 표) 갱신. graph 본체 gitignore 검증 (`git check-ignore` 통과).
- 코드 로직 변경 0건 → L1/L2 검증 N/A (verify 스크립트 대상 없음).
- 정리: 입력경로 하위 `src/graphify-out`·`docs/graphify-out` transient 캐시 제거(소스트리 오염·오커밋 방지).
# needs_user_verification
- (선택) `docs/graph/src/graph.html` / `docs/graph/docs/graph.html` 브라우저로 열어 그래프 시각 확인.
# graph_refresh
- 이번 세션이 graphify **최초 생성** (no-graph → 생성). scope: src, docs. graph-refresh-checker 는 본 작업이 곧 생성이므로 생략.
# User Signals
user_signals:
positive: []
negative:
- quote_or_paraphrase: '세레나 설정 및 그래피파이 생성을 안했더라고'
about: '직전 PR 세션에서 onboarding/graphify 누락. 단발 누락인지 절차 허점인지 retro 에서 판정.'
structural: false
# Retrospective
- signals:
positive:
- 'LSP 근인 진단(SSL MITM)→fix→TLS 검증→재시작 경로를 한 번에 수락'
negative:
- '직전 PR 세션에서 onboarding/graphify 누락 (이번 보강으로 해소, 단발)'
- what_went_well:
- '캐시 삭제로 안 풀리던 LSP 실패를 로그 traceback 까지 파고들어 진짜 근인(IntelliCode 호스트 SSL CERT_VERIFY_FAILED = 사내 Cloudflare Gateway MITM) 특정.'
- 'fix 를 텍스트 추정이 아니라 serena Python requests 로 실패 URL 실제 검증(status 200)한 뒤 적용 — §4.6 실행 검증 정신.'
- 'graphify scope src/docs 분리 + 자기 산출물(docs/graph/*) detect 제외 + 정적 이미지 제외로 노이즈 억제.'
- what_to_improve:
- 'graphify 가 입력경로 하위에 `graphify-out/` 캐시를 남김 — 매 실행 후 정리 필요(이번에 src/·docs/ 양쪽 발견). 워크플로 메모리에 명시.'
- 'LSP 실패를 첫 호출에서야 발견 — 세션 초반 Serena 활성화 직후 가벼운 probe 로 조기 감지 가능.'
- memory_candidates:
- { name: 'graphify_workflow', type: convention, signal_source: positive, description: 'graph 산출물 위치/스코프 + graphify-out 캐시 정리 함정', docs_sync_target: null }
- { name: '(host-global) serena LSP behind corporate SSL-inspection proxy', type: env, signal_source: positive, note: 'bibimbap 프로젝트 메모리엔 미반영 — 호스트 전역 이슈. report 에만 보존: CA 번들(certifi+keychain) → SSL_CERT_FILE/REQUESTS_CA_BUNDLE in MCP env.' }
- applied_changes:
- '~/.claude.json serena env 에 CA 번들 지정 (전역 — 사용자 승인)'
- 'serena memory `graphify_workflow` 추가 + core 에서 참조'
- protocol_feedback: []

2
.serena/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/cache
/project.local.yml

View File

@ -0,0 +1,22 @@
# Conventions — bibimbap
## Mapper (MyBatis)
- `@Mapper` 인터페이스 + 인라인 `@Select/@Insert/@Update/@Delete` (Java text block SQL). XML 매퍼·네임스페이스 없음.
- SELECT 에서 snake_case 컬럼을 camelCase 로 alias (`g.user_id AS userId`).
- 다중/명명 인자에 `@Param("x")`. 자동생성 PK: `@Options(useGeneratedKeys=true, keyProperty="id", keyColumn="id")`.
- 모든 쿼리에 soft-delete 가드 `is_delete IS NOT TRUE` (조인 대상 포함).
## Controller
- 생성자 주입(필드 `@Autowired` 미사용).
- 페이지 `@Controller`: view-name String 반환 (`"recruit-list"`, `"redirect:/login"`).
- JSON 엔드포인트: `ResponseEntity<Map<String,Object>>``LinkedHashMap``{status, message, ...}`. 헬퍼 `response(HttpStatus, msg)`.
- mutation(`@PostMapping`/`@DeleteMapping`)은 `@Transactional` + 표준 가드 순서:
1. `if (!CsrfTokens.isValid(request)) return 403 + CsrfTokens.errorBody()`
2. `sessionUserId(session)` null → 401
3. 소유권 불일치 → 403
- 입력 위생: `trimToNull`/`trimToEmpty` 헬퍼, 길이 상한 인라인 검사, `safeExternalUrl` 은 http/https 만 허용, 에셋 경로는 `/game/` 시작 + `..` 금지.
## 기타
- 사용자 노출 문자열 한국어.
- `data/` POJO 는 수기 getter/setter.
- soft-delete / 세션 인증 불변식은 `mem:core`.

31
.serena/memories/core.md Normal file
View File

@ -0,0 +1,31 @@
# Core — bibimbap
게임 쇼케이스 + 팀원 모집 웹앱 (org: com.pandoli365). 서버사이드 JSP 렌더 + JSON API 혼합. Spring Boot WAR + MyBatis + PostgreSQL.
## Source map (`src/main/java/com/pandoli365/bibimbap/`)
- `BibimbapApplication` / `ServletInitializer` — 부트 진입 + 외부 서블릿 컨테이너 배포용 initializer (packaging=war).
- `controller/``@Controller`. 페이지 컨트롤러(`RecruitController`, `WebMvcController`)는 JSP view-name 반환. `controller/api/` (`GameController`·`GameAssetController`·`GameUploadController`·`UserController` + `ApiExceptionControllerAdvice`)는 JSON/mutation 처리.
- `mapper/` — MyBatis `@Mapper` 인터페이스, 어노테이션 인라인 SQL. 상세 `mem:conventions`.
- `data/` — DB row/DTO POJO (`GameData`,`UserData`,`RecruitPostData`,`GameComment/LikeData`,`UserAuthIdentityData`). 수기 getter/setter (Lombok 미사용).
- `abstracts/` — 제네릭 베이스 `Request`/`Result`/`Service<Req,Res>`/`ErrorResult`.
- `game/GameCatalog` — DB 이전 레거시 게임 카탈로그(정적 배열 NAMES/CREATORS/...). DB 미존재 game id fallback.
- `security/CsrfTokens` — 수동 CSRF 토큰 util (Spring Security 없음).
- `config/UploadResourceConfig` — 업로드 WebGL/프로필 정적 리소스 핸들러.
- `webapp/WEB-INF/views/*.jsp` (+ `fragments/header.jspf`) — 뷰. ViewResolver prefix `/WEB-INF/views/`, suffix `.jsp`.
- `resources/{dev,live}/db.properties` — 프로필별 DB 자격증명 (gitignore; `.example` 만 커밋).
## 프로젝트 전역 불변식
- **Soft delete**: 조회는 `is_delete IS NOT TRUE` 필터, 삭제는 `is_delete=true, updated_at=now()`. `game_likes` 는 hard delete.
- **인증** = `HttpSession` attr `userId` (Spring Security 없음). mutation 엔드포인트는 첫 줄에 `CsrfTokens.isValid(request)` 체크 → 실패 시 403.
- **소유권** = session userId vs row userId 비교.
- 사용자 노출 문자열은 한국어.
## 참조
- 언어/프레임워크/빌드: `mem:tech_stack`
- 실행 명령: `mem:suggested_commands`
- 코드 컨벤션(mapper/controller/validation): `mem:conventions`
- 작업 완료 게이트: `mem:task_completion`
추가: 광범위한 `docs/` (ATP 문서체계 + 분석) 와 `docs/graph/` (graphify 산출물 메타) 존재.
graphify 그래프 산출물 위치·재생성·`graphify-out` 캐시 정리 함정: `mem:graphify_workflow`.

View File

@ -0,0 +1,9 @@
# Graphify Workflow — bibimbap
graphify 지식그래프 산출물 운용 규약.
- **위치**: 산출물은 `docs/graph/<scope>/{graph.html, graph.json, audit.md}`. 메타는 `docs/graph/index.md` (frontmatter `last_generated_at`/`source_commit`/`scopes` + Scopes 표).
- **추적**: graph 본체(html/json/audit)는 `docs/graph/.gitignore`**무시**(재생성 가능). `index.md``.gitignore` 만 커밋.
- **현재 scope**: `src` (Java 코드, 정적 이미지 제외), `docs` (md + DDL, 자기 산출물 `docs/graph/*` detect 제외).
- **함정**: `/graphify <path>` 는 입력경로 하위에 transient 캐시 `<path>/graphify-out/` (AST/semantic 캐시)를 남긴다. 실행 후 **반드시 `rm -rf <path>/graphify-out`** — 안 그러면 소스트리 오염 + 오커밋. (이번 세션에서 `src/graphify-out`·`docs/graphify-out` 발견.)
- 재생성 시 `index.md` 메타 갱신 필수. 폐기 scope 는 디렉토리 rm + 표에서 제거.

View File

@ -0,0 +1,33 @@
# Memory Maintenance
## Discovery Model
- Core principle: progressive discovery through references, building a graph of memories.
- Initially, agents are provided with the list of all memories (names only).
- Agents should read `mem:core` as the top-level entry point (graph root).
This memory should contain references to other memories covering major project domains.
The referenced memories shall, in turn, shall contain references to even more specific memories, and so on.
The depth of the graph shall depend on the project complexity.
- Use topics/folders to group related memories in order to make the content structure explicit.
Folders can mirror project structure (e.g. modules like frontend/backend) or topics like debugging, architecture, etc.
- Memory references must use a mem: prefix inside backticks, e.g. `mem:frontend/core`.
The surrounding text should clearly indicate when to read the memory/which content to expect.
The text should provide more precise guidance than the memory name alone,
i.e. avoid a reference like "frontend debugging: `mem:frontend/debugging` and instead make clear which aspects of frontend debugging are covered.
- Memories themselves should not contain information about when to read them; this is the responsibility of the referring memory.
## Style
Dense agent notes, not prose docs. Prefer invariants, terse bullets.
Avoid obvious context, rationale, and examples unless they prevent likely mistakes.
Keep guidance durable and generalizable, not task-local.
## Add/update threshold
Add or update memories only with stable, non-obvious project conventions that avoid complex rediscovery in the future.
Do not add: quick-read facts; generic language/framework knowledge; one-off task notes; volatile line-level details; behavior likely to change soon.
## Maintenance Actions
- Renaming memories: References are updated automatically if handled via Serena's memory rename tool.
- Checking for stale memories (e.g. after deletion): Call `serena memories check` for a report.

View File

@ -0,0 +1,14 @@
# Suggested Commands — bibimbap (Darwin)
빌드/실행 (Maven wrapper, 루트에서):
- `./mvnw spring-boot:run` — 개발 실행 (프로필 dev 기본).
- `./mvnw -Papp.profile=live spring-boot:run` — live 프로필.
- `./mvnw clean package` — WAR 빌드 → `target/bibimbap-0.0.1-SNAPSHOT.war`.
- `./mvnw test` — 전체 테스트.
- `./mvnw -Dtest=DbUpdateQueryGeneratorTest test` — 단일 테스트.
전제조건:
- 실행/통합테스트 전에 `src/main/resources/dev/db.properties``db.properties.example` 복사 후 작성 (gitignore). live 도 동일.
- PostgreSQL 기동 + 해당 스키마(dev/live) 접근 가능해야 datasource 로드 성공.
git/ls/grep/find 등 일반 유틸은 표준 unix 와 동일 — 생략.

View File

@ -0,0 +1,8 @@
# Task Completion — bibimbap
코딩 작업 완료 시:
1. `./mvnw test` 통과 (JUnit). `BibimbapApplicationTests` 는 컨텍스트 로드 — 유효 datasource(또는 스킵 조건) 필요.
2. `./mvnw clean package` 로 WAR 빌드 확인 — JSP/컴파일 오류는 단위테스트가 못 잡으므로 패키지까지 돌려 확인.
3. 린터/포매터 없음 (spotless/checkstyle 미설정) — 실행할 것 없음.
4. DB 스키마 변경 시: `src/test/db/dev-to-live-update.sql` + `DbUpdateQueryGeneratorTest` 패턴 참조 (dev→live 마이그레이션 쿼리 생성).
5. DB 접근 코드는 `dev/db.properties` + PostgreSQL 기동 전제. 미충족 시 통합 동작은 수동 검증 필요.

View File

@ -0,0 +1,12 @@
# Tech Stack — bibimbap
- **Java 21** (`maven.compiler.release=21`).
- **Spring Boot 3.5.14-SNAPSHOT** (`spring-boot.version`). spring-boot-starter-web.
- **Packaging = war**. `spring-boot-starter-tomcat` scope=**provided** + `ServletInitializer` → 외부 톰캣 배포 가능(개발은 내장 톰캣). finalName 미지정 → `target/bibimbap-0.0.1-SNAPSHOT.war`.
- **MyBatis**`mybatis-spring-boot-starter` 3.0.5. 어노테이션 매퍼만(XML 없음). `mybatis.configuration.log-impl=Slf4jImpl`, `logging.level.org.apache.ibatis=TRACE`.
- **DB** — PostgreSQL (`org.postgresql`). 프로필별 스키마 `currentSchema=dev|live`.
- **View** — JSP via `tomcat-embed-jasper`. ViewResolver prefix `/WEB-INF/views/`, suffix `.jsp`.
- **Lombok** 1.18.44 의존성 존재(단 `data/` POJO 는 수기 accessor).
- **빌드** — Maven wrapper `./mvnw`. Maven 프로필 `dev`(기본)/`live` → `app.profile` 설정 → 리소스 필터링 `@app.profile@` (application.properties `spring.profiles.active`).
- **테스트**`spring-boot-starter-test` (JUnit5).
- **업로드** — multipart/tomcat post size **1GB** (WebGL zip 업로드용). `app.webgl.asset-origin`/`frame-ancestors` 설정 존재.

View File

@ -1,18 +1,133 @@
project_name: bibimbap
language: java
build_system: maven
frameworks:
- Spring Boot
- JSP
- MyBatis
package_type: war
docs:
index: docs/index.md
latest_analysis: docs/analysis/2026-06-16-project-analysis.md
security_checklist: docs/security/security-remediation-checklist.md
guidelines:
- For documentation-only analysis, do not modify src/ or pom.xml.
- Record security findings with file:line evidence.
- Prefer rg for code and document search.
- Preserve user changes in the working tree.
- Split security remediation into small PRs with tests.
# the name by which the project can be referenced within Serena
project_name: "bibimbap"
# list of languages for which language servers are started; choose from:
# al angular ansible bash clojure
# cpp cpp_ccls crystal csharp csharp_omnisharp
# dart elixir elm erlang fortran
# fsharp go groovy haskell haxe
# hlsl html java json julia
# kotlin lean4 lua luau markdown
# matlab msl nix ocaml pascal
# perl php php_phpactor powershell python
# python_jedi python_ty r rego ruby
# ruby_solargraph rust scala scss solidity
# svelte swift systemverilog terraform toml
# typescript typescript_vts vue yaml zig
# (This list may be outdated. For the current list, see values of Language enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
# Note:
# - For C, use cpp
# - For JavaScript, use typescript
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
# Some languages require additional setup/installations.
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
# When using multiple languages, the first language server that supports a given file will be used for that file.
# The first language is the default language and the respective language server will be used as a fallback.
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- java
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
encoding: "utf-8"
# line ending convention to use when writing source files.
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
line_ending:
# The language backend to use for this project.
# If not set, the global setting from serena_config.yml is used.
# Valid values: LSP, JetBrains
# Note: the backend is fixed at startup. If a project with a different backend
# is activated post-init, an error will be returned.
language_backend:
# whether to use project's .gitignore files to ignore files
ignore_all_files_in_gitignore: true
# advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the options.
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
# No documentation on options means no options are available.
ls_specific_settings: {}
# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
# Paths can be absolute or relative to the project root.
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
# symbols and references across package boundaries.
# Currently supported for: TypeScript.
# Example:
# additional_workspace_folders:
# - ../sibling-package
# - ../shared-lib
additional_workspace_folders: []
# list of additional paths to ignore in this project.
# Same syntax as gitignore, so you can use * and **.
# Note: global ignored_paths from serena_config.yml are also applied additively.
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude.
# This extends the existing exclusions (e.g. from the global configuration)
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
excluded_tools: []
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
# This extends the existing inclusions (e.g. from the global configuration).
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
fixed_tools: []
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
# for this project.
# This setting can, in turn, be overridden by CLI parameters (--mode).
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
default_modes:
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
added_modes:
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# This overrides the corresponding setting in the global configuration; see the documentation there.
# If null or missing, use the setting from the global configuration.
symbol_info_budget:
# list of regex patterns which, when matched, mark a memory entry as readonly.
# Extends the list from the global configuration, merging the two lists.
read_only_memory_patterns: []
# list of regex patterns for memories to completely ignore.
# Matching memories will not appear in list_memories or activate_project output
# and cannot be accessed via read_memory or write_memory.
# To access ignored memory files, use the read_file tool on the raw file path.
# Extends the list from the global configuration, merging the two lists.
# Example: ["_archive/.*", "_episodes/.*"]
ignored_memory_patterns: []

View File

@ -31,3 +31,12 @@
- 문서 인덱스: `docs/index.md`
- 최신 프로젝트 분석: `docs/analysis/2026-06-16-project-analysis.md`
- 보안 개선 체크리스트: `docs/security/security-remediation-checklist.md`
<!-- atp:begin -->
## 문서화 정책 (docs-first)
작업 시작 전 `docs/index.md` → 카테고리 `index.md` → 구체 문서 순으로 읽는다.
## 에이전트 팀 운영
`/atp:task [요청]` 으로 Orchestrator+Advisor+Worker 팀 모드 진입(명시 호출 전용).
위임 토폴로지는 호스트 capability 자가판정(Tier A/A-flat/B)을 따른다 — 번들 `docs/development/platform-adapters.md`.
권위 레퍼런스: atp 플러그인 번들 `docs/development/agent-team-protocol.md`.
<!-- atp:end -->

View File

@ -1,31 +0,0 @@
# ADR
Architecture Decision Record를 보관한다.
## 작성 템플릿
```md
# ADR-NNN: 제목
## 상태
Proposed | Accepted | Superseded
## 배경
결정이 필요한 이유와 관련 근거를 적는다.
## 결정
선택한 방향을 적는다.
## 결과
장점, 단점, 후속 작업을 적는다.
```
## 후보
- ADR-001: WebGL asset 제공 origin 정책
- ADR-002: 서비스 레이어 도입 범위
- ADR-003: 좋아요/댓글 사용자 식별 정책

11
docs/adr/index.md Normal file
View File

@ -0,0 +1,11 @@
# ADR 인덱스
**카테고리 용도**: 기술 선택·아키텍처 원칙에 대한 되돌리기 어려운 결정. 불변(append-only). 결정을 뒤집을 때는 기존 ADR 을 수정하지 않고 새 ADR 을 발행하여 "supersedes ADR-NNNN" 명시.
파일명 규칙: `ADR-NNNN-kebab-case-title.md` (NNNN 은 0001 부터 순차 증가)
## 결정 목록
| ADR | 제목 | 상태 | 날짜 |
|---|---|---|---|
| *(아직 등록된 ADR 없음)* | | | |

View File

@ -1,179 +1,218 @@
# 2026-06-16 프로젝트 전면 분석
---
kind: analysis
title: bibimbap 종합 코드 분석 (아키텍처/보안/품질/도메인)
description: Spring Boot 3 MVC + MyBatis + PostgreSQL + JSP 웹앱의 4차원(D1~D4) 전면 read-only 분석. 보안 기초·기술부채·도메인 인벤토리·미결 결정 정리.
perspective: neutral
valid_starting_point_for: 현황 파악, 보안 결정 입력(login/signup CSRF·세션 쿠키·SNAPSHOT 고정), 기술부채 정리(dead code 제거·서비스 레이어 추출), 미완성 기능(게임 댓글/좋아요) 방향 결정
owner: art
stability: snapshot
last_reviewed: 2026-06-16
source: work-session 20260616-111711 (research-advisor, analysis.md) 에서 distill
superseded_note: 후속 결정으로 전제가 바뀌면 이 줄을 갱신한다 — 현재 없음
---
## 범위
# bibimbap 종합 코드 분석 (D1~D4)
- 목적: 현재 프로젝트의 아키텍처, 보안, 품질, 도메인 상태를 읽기 전용으로 분석하고 후속 보안 개선 항목을 정리한다.
- 변경 원칙: 이 분석 세션은 문서와 프로젝트 지침만 추가/수정한다. `src/``pom.xml`은 수정하지 않는다.
- 조사 방식: 파일 목록, 컨트롤러, 매퍼, JSP, 설정 파일을 `rg`와 라인 번호 기준으로 확인했다.
> 분석 방식: 30개 Java 파일 + 16개 JSP + 6개 MyBatis 매퍼 전수 직접 읽기(Read/Grep/Bash). 외부 자료 미사용 → 모든 사실은 코드 직접 확인(`확인됨`). 추정 항목은 `추정`/`미확인` 으로 명시했으며 사실로 격상하지 않았다.
>
> 출처: work-session `20260616-111711``research/analysis.md` 를 정제한 정본 기록.
## 요약
- 보안 기초는 비교적 견고하다. MyBatis 매퍼는 동적 `${}` 조립 없이 `#{}` 바인딩을 쓰고, 비밀번호는 PBKDF2-SHA256 210,000회 반복으로 저장한다.
- 주요 보안 갭은 `POST /login`, `POST /signup`에 CSRF 검증이 없는 점이다. 다른 주요 상태 변경 API는 `CsrfTokens.isValid`를 적용하고 있다.
- 파일 업로드와 WebGL asset 제공 경로는 normalize/startsWith 검사를 반복 적용하고, zip-slip 및 XSS 방어도 일관된 편이다.
- 구조적 부채는 서비스 레이어 부재, fat controller, 프로토타입 잔재(`abstracts`, `header.jspf`, `GameCatalog`), 실질 테스트 부족이다.
- 좋아요/댓글은 DB 매퍼와 삭제 연계는 있으나, 상세 화면은 `localStorage`만 사용해 서버 영속화가 미완성이다.
Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read-only 분석. 4개 차원으로 정리한다: 아키텍처(D1), 보안(D2, 최우선), 코드품질/기술부채(D3), 도메인/기능(D4).
## 1. 아키텍처 분석
상위 결론: **보안 기초는 의외로 탄탄**(SQLi 표면 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관)하나, **프로토타입 잔재성 dead code 와 미완성 기능이 화석으로 남아** 신규 개발자에게 혼란·인증 함정을 유발한다. 서비스 레이어 부재로 fat controller 가 발생했고, 테스트가 거의 전무하다.
### A1. 현재 구조는 Controller + Mapper 중심이다
## 주요 발견 Top 10
- Spring MVC JSP 뷰 구조는 `spring.mvc.view.prefix`와 suffix 설정에 고정되어 있다. 근거: `src/main/resources/application.properties:5`, `src/main/resources/application.properties:6`
- 홈/로그인/회원가입/프로필 라우팅은 `WebMvcController`가 담당한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/WebMvcController.java:57`, `src/main/java/com/pandoli365/bibimbap/controller/WebMvcController.java:64`, `src/main/java/com/pandoli365/bibimbap/controller/WebMvcController.java:75`, `src/main/java/com/pandoli365/bibimbap/controller/WebMvcController.java:88`
- 회원가입, 로그인, 프로필 변경은 `UserController`가 요청 검증, DB 접근, 세션 저장, 응답 생성을 함께 수행한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:65`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:118`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:173`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:209`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:494`
- 게임 등록/수정/삭제도 컨트롤러가 검증, 권한 확인, 매퍼 호출을 직접 수행한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:41`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:158`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:222`
- 모집글 등록도 동일 패턴이다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:49`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:73`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:116`
| # | 심각도 | 발견 | 위치 |
|---|--------|------|------|
| 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 태그(`<sec:authorize>`, `${_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 |
| 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 |
| 10 | INFO | **XSS 방어 일관 적용(비관습적)** — JSTL `<c:out>` 0건이나, 모든 JSP 가 Java scriptlet 변수 할당 시점에 `HtmlUtils.htmlEscape()` 적용 후 출력. 사용자 콘텐츠 sink(게임명/제작자/모집글/닉네임/이메일/창작노트/검색어) 전수 확인 결과 escape 누락 없음. CSP·X-Content-Type-Options 도 WebGL asset 응답에 설정. | views/*.jsp 전체, GameAssetController.java:60-66 |
영향: 단기 구현 속도는 빠르지만, 보안 검증과 도메인 규칙이 컨트롤러마다 흩어진다. CSRF 누락처럼 특정 엔드포인트만 빠지는 회귀가 발생하기 쉽다.
---
### A2. 정적 리소스 제공은 프로필과 게임 asset이 분리되어 있다
## D1 — 아키텍처/구조
- 프로필 이미지는 `UploadResourceConfig`에서 `/profile/**`로 노출한다. 근거: `src/main/java/com/pandoli365/bibimbap/config/UploadResourceConfig.java:19`, `src/main/java/com/pandoli365/bibimbap/config/UploadResourceConfig.java:22`
- WebGL 게임 asset은 별도 컨트롤러가 `/game/{gameUuid}/**`를 직접 처리한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:31`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:43`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:50`
- WebGL 응답에는 `X-Content-Type-Options`와 CSP가 설정된다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:59`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:60`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:65`
### 패키지별 책임 (1줄)
영향: 업로드 asset을 일반 정적 리소스와 분리한 점은 좋다. 단, 사용자가 만든 WebGL을 같은 사이트 origin에서 iframe으로 실행하므로 장기적으로 별도 asset origin 정책을 결정해야 한다.
| 패키지 | 역할 |
|--------|------|
| (root) | `BibimbapApplication`(@SpringBootApplication main), `ServletInitializer`(WAR 배포용 SpringBootServletInitializer) |
| abstracts | **dead code** — 미사용 제네릭 Service/Request/Result/ErrorResult 베이스 (확인됨: 참조 0) |
| 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(빈 배열)**, 레거시 정적 게임 카탈로그 폴백(현재 무력) |
| mapper | MyBatis `@Mapper` 인터페이스 6종 — 어노테이션 기반 SQL(XML 매퍼 0) |
| security | `CsrfTokens` — 세션 기반 CSRF 토큰 발급/검증 유틸(static) |
### A3. 프로토타입 잔재가 남아 있다
### 요청 흐름
- `GameController`는 DB에 게임이 없으면 `GameCatalog`로 fallback한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:103`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:111`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:116`
- `GameCatalog`는 빈 배열만 가진다. 근거: `src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java:8`, `src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java:12`, `src/main/java/com/pandoli365/bibimbap/game/GameCatalog.java:18`
- `header.jspf`는 Spring Security taglib와 `${_csrf}`를 전제로 하지만, 실제 화면은 `/WEB-INF/views/header.jsp`를 include한다. 근거: `src/main/webapp/WEB-INF/jsp/fragments/header.jspf:1`, `src/main/webapp/WEB-INF/jsp/fragments/header.jspf:2`, `src/main/webapp/WEB-INF/jsp/fragments/header.jspf:14`, `src/main/webapp/WEB-INF/views/login.jsp:203`, `src/main/webapp/WEB-INF/views/signup.jsp:201`
- `abstracts.Service`는 현재 컨트롤러 흐름과 별개인 수동 인증/요청 검증 추상화다. 근거: `src/main/java/com/pandoli365/bibimbap/abstracts/Service.java:5`, `src/main/java/com/pandoli365/bibimbap/abstracts/Service.java:7`, `src/main/java/com/pandoli365/bibimbap/abstracts/Service.java:11`, `src/main/java/com/pandoli365/bibimbap/abstracts/Service.java:12`
- **페이지 렌더**: Controller → (서비스 레이어 없음) → Mapper → DB → ModelAndView/Model → `/WEB-INF/views/*.jsp` (ViewResolver prefix/suffix 는 application.properties).
- **API mutation**: `@PostMapping`/`@DeleteMapping` → CSRF 검증 → 세션 인증 → 입력 검증 → Mapper → JSON ResponseEntity.
- **서비스 레이어 부재**(확인됨): 컨트롤러가 매퍼를 직접 주입·호출. `@Transactional` 은 컨트롤러 메서드에 부착. → requirements 가 "service 레이어 존재" 를 전제했다면 충돌(아래 종합 판단 참조).
- **JSP 렌더 방식**(확인됨): JSTL 미사용. Java scriptlet(`<%%>`/`<%=%>`)으로 `request.getAttribute()` 직접 캐스팅 + `HtmlUtils.htmlEscape()`.
영향: 죽은 코드가 보안 로직처럼 보이는 상태로 남아 있으면 새 구현자가 잘못 재사용할 수 있다. 삭제 전 참조 검색과 동작 확인이 필요하다.
### 레이어별 핵심 클래스
## 2. 보안 분석
| 레이어 | 클래스 | 역할(1줄) |
|--------|--------|----------|
| 진입 | BibimbapApplication / ServletInitializer | main 부트 / WAR 배포 부트 |
| 페이지 | WebMvcController | `/`,`/{pageName}`(화이트리스트), `/error`, 프로필·게임등록 뷰 게이팅 |
| 페이지+API | RecruitController | `/recruit`(목록), `/recruit/new`(폼+생성 POST), `/recruit/{id}`(상세) |
| API | GameController | 게임 생성/수정/삭제(soft) + 상세/편집 뷰 + 소유권 인가 |
| API | GameUploadController | WebGL zip 업로드(zip-slip 방어), 썸네일·일반파일 업로드 |
| API | GameAssetController | `/game/{uuid}/**` WebGL 정적 서빙 + CSP/인코딩 헤더 |
| API | UserController | signup/login/logout/프로필(닉네임·아바타) + PBKDF2 해싱 |
| API | ApiExceptionControllerAdvice | 업로드 초과·일반 예외 → JSON 에러(스택 비노출) |
| 매퍼 | Games/RecruitPosts/Users/UserAuthIdentities/GameComments/GameLikes Mapper | 어노테이션 SQL CRUD |
### S1. SQL injection 표면은 현재 스캔 범위에서 발견되지 않았다
### Spring/MyBatis 배선·프로파일
- `src/main/java/com/pandoli365/bibimbap/mapper`와 controller 패키지에서 MyBatis 동적 치환 `${}` 사용은 발견되지 않았다.
- 검색 쿼리도 `ILIKE CONCAT('%', #{query}, '%')`로 바인딩한다. 근거: `src/main/java/com/pandoli365/bibimbap/mapper/GamesMapper.java:85`, `src/main/java/com/pandoli365/bibimbap/mapper/GamesMapper.java:86`, `src/main/java/com/pandoli365/bibimbap/mapper/GamesMapper.java:87`
- 주요 insert/update/select는 `#{}` 바인딩을 사용한다. 근거: `src/main/java/com/pandoli365/bibimbap/mapper/UsersMapper.java:25`, `src/main/java/com/pandoli365/bibimbap/mapper/UsersMapper.java:39`, `src/main/java/com/pandoli365/bibimbap/mapper/UserAuthIdentitiesMapper.java:47`, `src/main/java/com/pandoli365/bibimbap/mapper/UserAuthIdentitiesMapper.java:67`, `src/main/java/com/pandoli365/bibimbap/mapper/GamesMapper.java:128`, `src/main/java/com/pandoli365/bibimbap/mapper/RecruitPostsMapper.java:91`
- `@SpringBootApplication` 단일. MyBatis 는 `mybatis-spring-boot-starter` 자동 배선 + `@Mapper` 스캔(별도 @MapperScan 불필요, 확인됨).
- 프로파일: pom `dev`/`live` 프로파일이 `app.profile` 치환 → `application.properties``spring.profiles.active=@app.profile@`(빌드 필터링) → `spring.config.import``${profile}/db.properties` 로드. dev/live 는 PostgreSQL 동일 호스트·다른 schema(`currentSchema=dev|live`).
- DB props: `db.properties.example` 만 git 추적, 실제 `db.properties` 는 .gitignore 처리(확인됨: gitignore `### Local secrets ###`).
- 앱 진입: embedded Tomcat(`spring-boot-starter-web`) + WAR(`tomcat-embed-jasper` for JSP, `spring-boot-starter-tomcat` provided). web.xml 없음(Servlet 3+ initializer).
판정: 현재 코드 스캔 기준 SQLi 직접 표면은 0이다. 동적 정렬/테이블명 기능을 추가할 때도 `${}`를 금지하는 규칙을 유지해야 한다.
---
### S2. 비밀번호 저장 방식은 강한 편이다
## D2 — 보안 (최우선)
- 최소 비밀번호 길이와 PBKDF2 파라미터가 상수로 정의되어 있다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:45`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:46`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:47`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:48`
- 신규 비밀번호는 salt 생성 후 `pbkdf2_sha256$iterations$salt$hash` 형식으로 저장된다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:542`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:543`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:546`
- 검증은 저장된 iteration/salt를 읽고 `MessageDigest.isEqual`로 비교한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:551`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:558`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:561`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:562`
- 알고리즘은 `PBKDF2WithHmacSHA256`이다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:568`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:571`
### 인증/인가
판정: 현재 구현은 기본 수준 이상이다. 향후에는 로그인 실패 제한, 비밀번호 재해시 정책, 계정 잠금 정책을 별도 개선으로 다루면 된다.
- **메커니즘**(확인됨): Spring Security 미사용(의존성 없음). `HttpSession` 속성 기반 커스텀. login 시 `request.changeSessionId()` 로 세션 고정 공격 방어(UserController.java:152). remember 옵션으로 세션 타임아웃 30분/30일 분기.
- **인가**(확인됨): 리소스 mutation 마다 `userId.equals(resource.getUserId())` 소유권 체크 일관 적용(GameController updateGame:183, deleteGame:239, editView:142). role 기반 인가는 미구현(role 컬럼은 저장만, 'USER' 고정).
### S3. CSRF 방어는 전반적으로 있으나 login/signup에 구멍이 있다
### 자격증명/시크릿
- CSRF 토큰은 세션에 저장되고 32바이트 난수로 생성된다. 근거: `src/main/java/com/pandoli365/bibimbap/security/CsrfTokens.java:20`, `src/main/java/com/pandoli365/bibimbap/security/CsrfTokens.java:28`, `src/main/java/com/pandoli365/bibimbap/security/CsrfTokens.java:31`
- 검증은 `X-CSRF-Token` 헤더 또는 `_csrf` 파라미터를 허용한다. 근거: `src/main/java/com/pandoli365/bibimbap/security/CsrfTokens.java:35`, `src/main/java/com/pandoli365/bibimbap/security/CsrfTokens.java:44`, `src/main/java/com/pandoli365/bibimbap/security/CsrfTokens.java:46`, `src/main/java/com/pandoli365/bibimbap/security/CsrfTokens.java:48`
- JSP 공통 초기화는 meta token과 JS header helper를 제공한다. 근거: `src/main/webapp/WEB-INF/views/theme-init.jsp:5`, `src/main/webapp/WEB-INF/views/theme-init.jsp:7`, `src/main/webapp/WEB-INF/views/theme-init.jsp:19`, `src/main/webapp/WEB-INF/views/theme-init.jsp:27`
- 로그아웃, 프로필 변경, 게임 업로드, 게임 등록/수정/삭제, 모집글 등록은 CSRF 검증을 한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:164`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:179`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:215`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:59`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:106`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:170`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:53`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:171`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:227`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:65`
- `POST /signup`은 컨트롤러 진입 후 CSRF 검증 없이 입력 검증과 DB 조회를 시작한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:65`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:75`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:87`
- `POST /login`도 CSRF 검증 없이 인증 로직과 세션 갱신으로 이어진다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:118`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:126`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:151`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:152`
- 로그인/회원가입 fetch 요청은 현재 CSRF header helper를 쓰지 않는다. 근거: `src/main/webapp/WEB-INF/views/login.jsp:277`, `src/main/webapp/WEB-INF/views/login.jsp:279`, `src/main/webapp/WEB-INF/views/login.jsp:284`, `src/main/webapp/WEB-INF/views/signup.jsp:291`, `src/main/webapp/WEB-INF/views/signup.jsp:293`, `src/main/webapp/WEB-INF/views/signup.jsp:297`
- 하드코딩 시크릿 **없음**(확인됨): tracked 파일은 `*.example`(placeholder `your_username/your_password`)뿐. 실제 db.properties 는 gitignore. application.properties 에 비밀값 없음.
- 비밀번호 저장: PBKDF2-SHA256 210k iter(상기 Top#2). 평문·약한해시 없음.
판정: MED. 공격자가 피해자의 브라우저에서 원치 않는 로그인 상태 전환 또는 회원가입 요청을 유도할 수 있다. 기존 CSRF 헬퍼가 있어 수정 범위는 작다.
### SQL 인젝션 (전수 — `${` vs `#{`)
### S4. 업로드와 파일 경로 방어는 일관되어 있다
- **전 매퍼 `${}` 보간 0건**(확인됨, grep 전수). 모든 동적값은 `#{}` 또는 `@Param`+`#{}`. 검색: `searchVisibleGames``ILIKE CONCAT('%', #{query}, '%')` 로 안전(GamesMapper.java:85-87).
- 단, **테스트 코드** `DbUpdateQueryGeneratorTest``String.formatted()` 로 schema 명을 SQL 에 직접 삽입(line 333,369) + `quoteIdentifier`/`escapeSql` 로 수동 이스케이프. 이는 프로덕션 경로 아님(테스트 전용, surefire 에서 제외됨)이나 패턴상 주의(LOW, 입력이 상수 'dev'/'live' 라 실위험 없음 — `확인됨`).
- 게임 파일 업로드는 CSRF와 로그인 세션을 먼저 확인한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:59`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:62`
- WebGL zip 업로드는 zip 여부, 로그인, target path boundary를 확인한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:106`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:113`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:121`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:127`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:128`
- zip entry 개수와 압축 해제 총량 제한이 있다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:40`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:41`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:261`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:297`
- zip-slip 방어는 `targetDir.resolve(entry.getName()).normalize()``startsWith(targetDir)`로 수행된다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:266`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:267`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:268`
- 썸네일/프로필 이미지는 크기와 확장자/Content-Type 검사를 한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:181`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:186`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:228`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:233`
- 프로필 저장 경로도 root boundary를 검사한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:246`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:248`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:254`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:256`
- 게임 asset 조회도 URI decode 후 normalize/startsWith를 수행한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:91`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:92`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:101`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameAssetController.java:102`
### JSP XSS / CSRF
판정: 현재 방어는 좋다. 다만 운영 설정에서 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`
- **XSS**(확인됨): `<c:out>` 0건이나 모든 사용자 콘텐츠 sink 가 `HtmlUtils.htmlEscape()` 처리(index.jsp:506-507, recruit-detail.jsp:전 필드, recruit-list.jsp:286-292, profile.jsp:16-18, header.jsp:11, game-detail.jsp:7-21, game-register.jsp:13-17, theme-init.jsp:7). raw `<%=` sink 들도 추적 결과 모두 escape 된 변수 또는 안전값(ctx, id, String.format)임을 확인.
- 게임 댓글: 클라이언트 JS 가 `textContent` 로 렌더(game-detail.jsp:970,983) → DOM XSS 안전. 단 서버 미저장.
- **CSRF**(확인됨): 커스텀 더블서밋 — `theme-init.jsp``<meta name="csrf-token">` + `window.BibimbapCsrf.headers()``X-CSRF-Token` 헤더 주입. 검증은 `CsrfTokens.isValid()`(헤더 또는 `_csrf` 파라미터, 세션값과 `.equals` 비교). **단 login/signup 미적용**(Top#3, MED).
- 세션/쿠키: 기본 JSESSIONID. `Secure`/`SameSite` 명시 설정 없음(application.properties 미존재 → 컨테이너 기본값. 프로덕션 HTTPS 에서 Secure 미설정이면 LOW~MED 위험 — **추정**: 배포 톰캣 설정 미확인이라 코드만으로 단정 불가).
### S5. XSS 방어는 서버 렌더와 클라이언트 렌더 모두 대체로 안전하다
### 파일 업로드 보안 (양호)
- 게임 상세 주요 출력값은 `HtmlUtils.htmlEscape`로 이스케이프한다. 근거: `src/main/webapp/WEB-INF/views/game-detail.jsp:6`, `src/main/webapp/WEB-INF/views/game-detail.jsp:7`, `src/main/webapp/WEB-INF/views/game-detail.jsp:10`, `src/main/webapp/WEB-INF/views/game-detail.jsp:21`
- 홈 검색어와 게임 카드 출력도 escape를 적용한다. 근거: `src/main/webapp/WEB-INF/views/index.jsp:21`, `src/main/webapp/WEB-INF/views/index.jsp:466`, `src/main/webapp/WEB-INF/views/index.jsp:506`, `src/main/webapp/WEB-INF/views/index.jsp:507`, `src/main/webapp/WEB-INF/views/index.jsp:514`
- 댓글 localStorage 렌더링은 `textContent`를 사용한다. 근거: `src/main/webapp/WEB-INF/views/game-detail.jsp:951`, `src/main/webapp/WEB-INF/views/game-detail.jsp:958`, `src/main/webapp/WEB-INF/views/game-detail.jsp:969`
- 프로필 아바타 URL은 출력 전에 escape한다. 근거: `src/main/webapp/WEB-INF/views/header.jsp:8`, `src/main/webapp/WEB-INF/views/header.jsp:11`, `src/main/webapp/WEB-INF/views/header.jsp:219`
- 외부 Git URL은 `http://` 또는 `https://`만 허용한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:282`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:287`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:288`
- zip-slip 방어: `target.startsWith(targetDir)` 정규화 검증(GameUploadController.java:128,200,266-269).
- zip bomb 방어: entry 수 8000 / 압축해제 512MB 상한(line 40-41,262,297).
- path traversal: 프로필 아바타·에셋 서빙 모두 `normalize()`+`startsWith(root)` 검증(UserController.java:248-263, GameAssetController.java:101-104). UUID 검증으로 게임 디렉토리 화이트리스트.
- 콘텐츠 타입: 확장자 화이트리스트(png/jpg/webp/gif). **추정 한계**: MIME/확장자만 검사, 매직바이트 미검증 → polyglot 업로드 가능성(LOW, WebGL asset 은 nosniff+CSP 로 완화).
판정: 현재 XSS 방어는 일관된 편이다. 서버 댓글 영속화를 붙일 때도 JSP 직접 출력 대신 escape/textContent 원칙을 유지해야 한다.
### 정정 (orientation 대비)
### S6. 세션 보안은 기본은 있으나 쿠키 하드닝 설정이 없다
- orientation 은 "JSP under WEB-INF/jsp(+fragments)" 를 활성 뷰처럼 기재했으나, 실제 활성 뷰는 `WEB-INF/views/*.jsp` 이고 `WEB-INF/jsp/fragments/header.jspf`**orphan dead 잔재**(Top#5)임을 확인.
- 로그인 성공 시 `request.changeSessionId()`로 세션 고정 공격을 줄인다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:151`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:152`
- 로그인 유지 옵션은 세션 만료 시간을 조정한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:49`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:50`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:317`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:318`
- `application.properties`에는 session cookie `HttpOnly`, `Secure`, `SameSite` 설정이 없다. 근거: `src/main/resources/application.properties:1`, `src/main/resources/application.properties:27`
---
판정: B4에서 운영 프로필 기준 쿠키 하드닝을 추가한다.
## D3 — 코드 품질/기술부채
### S7. 의존성/로깅 운영 리스크가 있다
| 항목 | 발견 | 위치 |
|------|------|------|
| Dead code | abstracts 패키지 4클래스 전부 미사용; GameCatalog 빈 stub+도달불가 분기; header.jspf orphan | Top#4,5,6 |
| 미완성 기능 | game_comments 스키마·매퍼·DTO 존재하나 컨트롤러 미연결(localStorage 임시) | Top#7 |
| 레이어 위반 | 서비스 레이어 부재 → 컨트롤러가 비즈니스 로직+매퍼 직접 호출(fat controller). UserController 629라인(해싱·파일IO·세션관리·검증 혼재) | UserController.java 전체 |
| 중복 | `sessionUserId()` 동일 메서드가 4개 컨트롤러에 복붙(RecruitController:155, GameController:291, GameUploadController:222, UserController:325, WebMvcController:118). `trimToNull/trimToEmpty`, `imageExtension/profileImageExtension`, UUID 정규화도 중복 | 다수 |
| 미사용 의존성 | Lombok 이 pom+컴파일러 플러그인에 선언되었으나 data 클래스는 수동 getter/setter(Lombok 미활용) | pom.xml:74-79, data/*.java |
| 오타/네이밍 | 뷰 파일명 `errer.jsp`(error 오타), 메서드명 `ChackService`(Check 오타), 필드 `is_login`(자바 네이밍 컨벤션 위반 snake_case) | views/errer.jsp, abstracts/Service.java:7,11 |
| 미사용 코드 | `WebMvcController.isMobileDevice()` 정의만 되고 호출 없음(line 130) | WebMvcController.java:130 |
| 에러 처리 | API 는 일관(ResponseEntity+JSON, 스택 비노출). 단 `Result` 생성자의 `System.out.println("잘못된 status")`(line 21)는 로깅이 아닌 stdout | abstracts/Result.java:21 |
| 매직값 | 역할/참여유형/상태 한글 문자열 하드코딩 Set(RecruitController:25-27); status 1000("NULL USERS") 같은 비표준 코드 | RecruitController.java:25-27 |
| 설정 위험 | 업로드 1GB 상한(application.properties), `max-swallow-size=-1`(무제한) → DoS 표면(**추정**: 인증 게이트 뒤라 완화되나 큰 값) | application.properties |
- Spring Boot 버전이 SNAPSHOT이다. 근거: `pom.xml:29`
- snapshot repository와 pluginRepository가 활성화되어 있다. 근거: `pom.xml:200`, `pom.xml:202`, `pom.xml:210`, `pom.xml:212`
- MyBatis TRACE 로그가 켜져 있다. 근거: `src/main/resources/application.properties:25`, `src/main/resources/application.properties:26`, `src/main/resources/application.properties:27`
### 테스트
판정: 배포 안정성과 보안 재현성을 위해 release 버전 고정, CVE 스캔, 운영 로깅 레벨 분리가 필요하다.
- `BibimbapApplicationTests.contextLoads()` — 스모크만(빈 본문).
- `DbUpdateQueryGeneratorTest` — 실제 단위테스트 아님. dev/live DB 직접 연결해 schema/data diff SQL 을 `src/test/db/dev-to-live-update.sql` 로 출력하는 **운영 도구**(surefire 에서 제외, pom 명시). 실DB 필요 → CI 불가. (참조: [db-update-query-generator.md](../db-update-query-generator.md))
- **커버리지 갭**: 컨트롤러·매퍼·인증·업로드·CSRF 로직 단위/통합 테스트 전무. 보안 핵심(PBKDF2 검증, zip-slip, 소유권 인가) 미검증.
## 3. 품질 분석
### pom 위생
### Q1. 테스트가 실질 보안/도메인 동작을 검증하지 않는다
- **spring-boot 3.5.14-SNAPSHOT**(확인됨) — SNAPSHOT 은 비재현·비프로덕션. spring-snapshots 저장소 의존. 안정 릴리스로 고정 필요(MED 부채, **추정**: 보안패치 추적 어려움).
- Java 21, MyBatis starter 3.0.5, PostgreSQL(runtime), 의존성 수 적고 깔끔. CVE 직접 스캔 미수행(외부 조회 안 함 — `미확인`).
- 기본 테스트는 context load뿐이다. 근거: `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java:6`, `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java:9`, `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java:10`
- DB 업데이트 쿼리 생성 테스트는 surefire에서 제외되어 일반 테스트로 돌지 않는다. 근거: `pom.xml:127`, `pom.xml:130`, `pom.xml:131`
- CSRF, 로그인, 업로드 zip-slip, XSS 렌더링 회귀 테스트가 없다. 근거: 현재 `src/test/java/com/pandoli365/bibimbap/BibimbapApplicationTests.java:9` 외 애플리케이션 동작 테스트가 확인되지 않는다.
---
영향: 보안 개선 PR마다 최소 MockMvc 테스트를 붙여 회귀를 잡아야 한다.
## D4 — 도메인/기능
### Q2. 컨트롤러에 중복 helper가 많다
### 기능 인벤토리 (기능 → 엔드포인트 / 매퍼 / 뷰 / 데이터)
- 세션 사용자 ID 추출이 여러 컨트롤러에 반복된다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:325`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:222`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:291`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:155`
- trim helper도 반복된다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:317`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:173`
| 기능 | 엔드포인트 | 매퍼 | 뷰 | 데이터/테이블 |
|------|-----------|------|-----|--------------|
| 게임 목록/검색 | GET `/`(q 검색) | GamesMapper.getVisibleGames/searchVisibleGames | index.jsp | games, users |
| 게임 상세 | GET `/game/{id}` | getGame | game-detail.jsp | games, users |
| 게임 등록 | GET `/game/new`(뷰), POST `/game/new` | addGame, nextSortOrder | game-register.jsp | games |
| 게임 수정/삭제 | GET/POST `/game/{id}/edit`, DELETE `/game/{id}` | updateGame, softDeleteGame, softDeleteGameComments, deleteGameLikes | game-register.jsp(edit) | games, game_comments, game_likes |
| WebGL 업로드 | POST `/api/game-files`, `/webgl-zip`, `/thumbnail`, GET `/ping` | — (파일시스템) | (AJAX) | 업로드 디렉토리 |
| WebGL 서빙 | GET `/game/{uuid}/**` | — | (iframe) | 파일시스템 |
| 팀원 모집 목록/상세 | GET `/recruit`, `/recruit/{id}` | getVisibleRecruitPosts, getRecruitPost | recruit-list.jsp, recruit-detail.jsp | recruit_posts, users |
| 모집글 작성 | GET/POST `/recruit/new` | addRecruitPost, nextSortOrder | recruit-form.jsp | recruit_posts |
| 회원가입/로그인/로그아웃 | POST `/signup`,`/login`,`/logout` | Users/UserAuthIdentities add/get/update | signup.jsp, login.jsp | users, user_auth_identities |
| 프로필 | GET `/profile`(뷰), POST `/profile/nickname`,`/profile/avatar` | getUser, updateUser, getGamesByUserId | profile.jsp | users, user_auth_identities |
| 정적 페이지 | GET `/terms`,`/operation-policy` | — | terms.jsp, operation-policy.jsp | — |
| 게임 댓글 | **(엔드포인트 없음)** | GameCommentsMapper(미연결) | game-detail.jsp(localStorage) | game_comments(미사용) |
| 게임 좋아요 | **(엔드포인트 없음)** | GameLikesMapper(deleteGameLikes 만 호출) | game-detail.jsp(JS baseLikes) | game_likes(쓰기경로 없음) |
영향: 정책 변경 시 수정 누락 위험이 높다. 서비스/유틸 레이어 도입은 보안 변경 뒤 별도 PR로 다루는 편이 안전하다.
### 데이터 모델 (매퍼 SQL 에서 역추출 — `확인됨`)
### Q3. 설정 상한과 실제 검증 상한이 어긋난다
- **users**(id, display_name, canonical_email, avatar_url, role, status, last_login_at, created_at, updated_at, is_delete)
- **user_auth_identities**(id, user_id, provider, provider_user_id, email, password_hash, display_name, avatar_url, last_login_at, created_at, updated_at, is_delete) — provider='email' 고정, 소셜 확장 의도된 스키마(provider/provider_user_id)이나 email 만 구현(**추정**). 상세: [user-signup-schema.md](../user-signup-schema.md)
- **games**(id, user_id, name, creator_note, git_url, webgl_path, thumbnail_url, like_count, is_visible, sort_order, created_at, updated_at, is_delete)
- **recruit_posts**(id, user_id, project_name, genre, summary, role, project_status, participation_type, expected_period, team_members, contact, description, reference_url, deadline_at, is_visible, sort_order, created_at, updated_at, is_delete)
- **game_comments**(id, game_id, nickname, content, created_at, deleted_at, is_delete) — 매퍼는 완성, 미연결
- **game_likes**(id, game_id, user_key, created_at)
- 공통 패턴: `is_delete` soft-delete + `is_visible` 노출제어 + `sort_order` 정렬. 모든 조회가 `is_delete IS NOT TRUE` 필터(확인됨, 일관).
- Spring multipart 상한은 1GB다. 근거: `src/main/resources/application.properties:15`, `src/main/resources/application.properties:16`
- WebGL zip 압축 해제 총량 제한은 512MB다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:40`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameUploadController.java:297`
### 사용자 플로우
영향: 애플리케이션 레벨에서 거절하기 전 서버가 큰 요청을 받아야 한다. 운영 인프라 제한과 함께 조정해야 한다.
1. 비로그인: 홈에서 게임 탐색/검색 → 게임 상세(WebGL 플레이, 댓글은 로컬), 모집 목록/상세 열람.
2. 가입(이메일+비번 8자+약관) → 로그인(세션, remember) → 프로필(닉네임/아바타 변경).
3. 로그인 사용자: 게임 등록(zip 업로드→썸네일→메타 저장), 본인 게임 수정/삭제, 모집글 작성.
4. 인가: 게임 수정/삭제는 작성자만(소유권 체크).
## 4. 도메인 분석
---
### D1. 회원가입 문서는 다중 provider를 설명하지만 구현은 email provider 중심이다
## 종합 판단 (상위 패턴 · 충돌 · 갭)
- 문서는 `guest`, `google`, `email`, `kakao`, `naver`, `github`, `apple`를 설명한다. 근거: `docs/user-signup-schema.md:32`
- 현재 컨트롤러는 `PROVIDER_EMAIL` 상수로 email provider를 사용한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:42`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:87`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:100`
- 회원가입 JSP에는 `provider` hidden input이 있지만 컨트롤러는 해당 파라미터를 받지 않는다. 근거: `src/main/webapp/WEB-INF/views/signup.jsp:216`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:67`, `src/main/java/com/pandoli365/bibimbap/controller/api/UserController.java:72`
**강점**: 보안 기초가 의외로 탄탄하다 — SQLi 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관, 세션 고정 방어, 소유권 인가 일관. 코드가 "보안을 의식하고 작성" 된 흔적이 뚜렷.
영향: 문서와 구현 간 목표 차이가 있다. 소셜/게스트 가입을 실제 기능으로 만들지, 문서를 email-only로 조정할지 결정해야 한다.
**핵심 부채 패턴**: 프로토타입 잔재가 곳곳에 화석으로 남음 — (a)abstracts 미사용 프레임워크 시도, (b)GameCatalog 정적 카탈로그→DB 마이그레이션 후 stub 만 잔존, (c)Spring Security 기반 header.jspf orphan, (d)game_comments/game_likes 서버 기능 미완성(스키마만). 이들은 "구현되다 만/대체된" 코드로, 신규 개발자에게 혼란·인증함정(abstracts 의 'id' 키)을 유발.
### D2. 게임 등록/수정/삭제는 서버 영속화되어 있다
**아키텍처 갭**: 서비스 레이어 부재로 fat controller(특히 UserController 629줄). 중복 헬퍼(sessionUserId 5중복) → 공통 유틸/베이스 추출 여지. 테스트 거의 전무가 가장 큰 리스크(보안 로직 회귀 무방비).
- 게임 생성은 `gamesMapper.addGame`으로 저장한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:80`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:90`, `src/main/java/com/pandoli365/bibimbap/mapper/GamesMapper.java:117`
- 게임 수정은 작성자 확인 후 `gamesMapper.updateGame`을 호출한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:179`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:183`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:212`
- 게임 삭제는 댓글 soft delete와 좋아요 삭제를 함께 수행한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:243`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:244`, `src/main/java/com/pandoli365/bibimbap/controller/api/GameController.java:245`
**보안 미결 결정**: login/signup CSRF 적용 여부, 세션쿠키 Secure/SameSite, spring-boot SNAPSHOT 고정 — design 단계 보안 결정 필요.
영향: 게임 CRUD는 서버 중심으로 동작한다. 좋아요/댓글만 연결되지 않은 상태다.
**requirements 충돌 가능**: "레이어드 아키텍처(controller/service/mapper)" 전제가 있었다면, 실제로 service 레이어는 없음(abstracts 는 dead). → 후속 작업에서 전제 재확인 필요.
### D3. 좋아요/댓글은 DB 모델은 있으나 상세 화면은 localStorage 전용이다
## 미해결 / open_questions
- 좋아요 매퍼는 존재한다. 근거: `src/main/java/com/pandoli365/bibimbap/mapper/GameLikesMapper.java:10`, `src/main/java/com/pandoli365/bibimbap/mapper/GameLikesMapper.java:24`, `src/main/java/com/pandoli365/bibimbap/mapper/GameLikesMapper.java:36`
- 댓글 매퍼는 존재한다. 근거: `src/main/java/com/pandoli365/bibimbap/mapper/GameCommentsMapper.java:10`, `src/main/java/com/pandoli365/bibimbap/mapper/GameCommentsMapper.java:27`, `src/main/java/com/pandoli365/bibimbap/mapper/GameCommentsMapper.java:41`
- 상세 화면 좋아요는 `localStorage` map만 갱신한다. 근거: `src/main/webapp/WEB-INF/views/game-detail.jsp:811`, `src/main/webapp/WEB-INF/views/game-detail.jsp:812`, `src/main/webapp/WEB-INF/views/game-detail.jsp:817`, `src/main/webapp/WEB-INF/views/game-detail.jsp:830`, `src/main/webapp/WEB-INF/views/game-detail.jsp:904`, `src/main/webapp/WEB-INF/views/game-detail.jsp:906`
- 상세 화면 댓글도 `localStorage`에 저장하고 DOM으로 렌더링한다. 근거: `src/main/webapp/WEB-INF/views/game-detail.jsp:813`, `src/main/webapp/WEB-INF/views/game-detail.jsp:913`, `src/main/webapp/WEB-INF/views/game-detail.jsp:928`, `src/main/webapp/WEB-INF/views/game-detail.jsp:994`, `src/main/webapp/WEB-INF/views/game-detail.jsp:1002`
1. 게임 좋아요·댓글의 서버 영속화는 의도된 미완성인가, 폐기된 기능인가? (스키마·매퍼는 완비, 엔드포인트만 없음) — 코드만으로 의도 판별 불가(`미확인`).
2. 세션 쿠키 Secure/SameSite·HTTPS 강제는 배포 톰캣/리버스프록시 설정에 의존 — 저장소 코드 밖이라 `미확인`. 프로덕션 설정 확인 필요.
3. `provider`/`provider_user_id` 컬럼 = 소셜로그인 확장 예정 스키마인지(현재 email 전용) — `추정`.
4. spring-boot 3.5.14-SNAPSHOT 을 의도적으로 SNAPSHOT 유지하는 이유(특정 미릴리스 픽스 의존?) — `미확인`.
5. 의존성 CVE 스캔 미수행(외부 조회 안 함) — 별도 `mvn dependency-check` 또는 OSV 조회 권장.
영향: 사용자는 브라우저를 바꾸면 좋아요/댓글을 잃는다. 서버 연결 시 익명 허용 여부, 남용 방지, 삭제 권한, CSRF 정책을 먼저 확정해야 한다.
## 관련 링크 (References)
### D4. 모집글은 서버 영속화되어 있다
- 목록, 작성, 상세 라우트가 존재한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:35`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:49`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:145`
- 작성 시 enum-like set과 길이 검증을 수행한다. 근거: `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:25`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:26`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:27`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:80`, `src/main/java/com/pandoli365/bibimbap/controller/RecruitController.java:112`
- 매퍼는 visible/not-deleted 조건으로 조회한다. 근거: `src/main/java/com/pandoli365/bibimbap/mapper/RecruitPostsMapper.java:67`, `src/main/java/com/pandoli365/bibimbap/mapper/RecruitPostsMapper.java:68`, `src/main/java/com/pandoli365/bibimbap/mapper/RecruitPostsMapper.java:74`
영향: 모집글 쪽은 최소 CRUD 흐름이 게임 CRUD와 비슷한 수준으로 정리되어 있다.
## 후속 작업 후보
- B1: `login`/`signup` CSRF 검증 추가. 자세한 실행 항목은 [보안 개선 체크리스트](../security/security-remediation-checklist.md#b1-loginsignup-csrf-검증-추가-med)를 따른다.
- B2: 프로토타입 dead code 제거. 자세한 실행 항목은 [보안 개선 체크리스트](../security/security-remediation-checklist.md#b2-프로토타입-dead-code-제거)를 따른다.
- B3: 좋아요/댓글 서버 영속화 연결. 자세한 실행 항목은 [보안 개선 체크리스트](../security/security-remediation-checklist.md#b3-좋아요댓글-서버-영속화-연결)를 따른다.
- B4: Spring Boot SNAPSHOT 고정, 세션 쿠키 하드닝, 의존성 CVE 스캔. 자세한 실행 항목은 [보안 개선 체크리스트](../security/security-remediation-checklist.md#b4-의존성세션운영-하드닝)를 따른다.
- 출처(work-session): `.atp/work-session/20260616-111711/research/analysis.md`
- [db-update-query-generator.md](../db-update-query-generator.md) — `DbUpdateQueryGeneratorTest` 운영 도구(D3 테스트 항목 관련)
- [user-signup-schema.md](../user-signup-schema.md) — `users`/`user_auth_identities` 스키마 상세(D4 데이터 모델 관련)
- 카테고리 분류 기준: [development/document-category-classification.md](../development/document-category-classification.md)

View File

@ -1,14 +0,0 @@
# Analysis
프로젝트 전면 분석, 감사 기록, 리스크 인벤토리를 보관한다.
## 문서
- [2026-06-16 프로젝트 전면 분석](2026-06-16-project-analysis.md)
## 작성 규칙
- 분석 범위와 변경 범위를 먼저 적는다.
- 발견 사항은 아키텍처, 보안, 품질, 도메인 축으로 분류한다.
- 모든 발견은 `file:line` 근거를 포함한다.
- 후속 작업은 보안/기능/리팩터링 PR로 분리할 수 있게 체크리스트와 연결한다.

7
docs/analysis/index.md Normal file
View File

@ -0,0 +1,7 @@
# Analysis — 코드/흐름/성능/리스크 분석
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
- [2026-06-16-project-analysis.md](./2026-06-16-project-analysis.md) — bibimbap 전면 코드 분석(D1 아키텍처/D2 보안/D3 품질·기술부채/D4 도메인). 보안 기초는 견고(SQLi 0·PBKDF2·CSRF 더블서밋)하나 프로토타입 잔재 dead code·미완성 기능·서비스 레이어 부재가 주요 부채. `perspective: neutral`, work-session 20260616-111711 distill.

View File

@ -1,21 +0,0 @@
# Architecture
현재 구조와 향후 아키텍처 결정 기록으로 이어지는 문서를 보관한다.
## 현재 구조 요약
- Spring Boot WAR 패키징, JSP ViewResolver, MyBatis mapper 중심 구조.
- Controller가 요청 검증, 세션 확인, 도메인 처리, mapper 호출을 직접 수행한다.
- 프로필 이미지는 `/profile/**` resource handler로 제공하고, WebGL 게임 asset은 `GameAssetController`가 직접 제공한다.
## 관련 문서
- [2026-06-16 프로젝트 전면 분석](../analysis/2026-06-16-project-analysis.md)
- [보안 개선 체크리스트](../security/security-remediation-checklist.md)
## 후속 ADR 후보
- 서비스 레이어 도입 기준
- WebGL asset 별도 origin 분리 여부
- 좋아요/댓글 익명 허용 정책
- 회원가입 provider 범위

View File

@ -0,0 +1,22 @@
---
kind: architecture
title: Architecture 카테고리 인덱스
description: 시스템 경계·저장 구조·구성 파일 맵 등 아키텍처 문서 인덱스.
owner: template-maintainer
stability: stable
last_reviewed: 2026-05-07
---
# Architecture — 시스템 경계 / 저장 구조 / 구성 파일 맵
이 카테고리는 본 템플릿의 **오래 유지될 구조 설명** 을 모은다. 런타임 동작 변경 이력은 `changes/`, 기술 선택 결정은 `adr/` 로 분리.
## 목록
- [file-map.md](./file-map.md) — 템플릿 루트의 구성 파일 트리 + 파일별 역할 + 런타임 생성 디렉토리 (원 위치: 과거 README §3)
## 관련 카테고리
- 기술/운영 결정의 **불변 레코드**`../adr/`
- 재사용 가능한 **개발 규칙**`../development/`
- graphify 산출물 메타는 `../graph/`

7
docs/backlog/index.md Normal file
View File

@ -0,0 +1,7 @@
# Backlog — 미채택 아이디어, 재검토 트리거
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

7
docs/changes/index.md Normal file
View File

@ -0,0 +1,7 @@
# Changes — 실제 동작이 바뀐 구현 변경 이력
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

7
docs/contracts/index.md Normal file
View File

@ -0,0 +1,7 @@
# Contracts — 외부/내부 계약 스펙
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

View File

@ -102,3 +102,7 @@ $env:JAVA_HOME='C:\Users\acst0\.jdks\azul-21.0.10'
- 따라서 SQL을 실행할 때는 반드시 `live` 스키마가 기본 스키마로 잡힌 연결에서 실행해야 한다.
- `dev` DB 접속 정보는 `src/main/resources/dev/db.properties`를 사용한다.
- `live` DB 접속 정보는 `src/main/resources/live/db.properties`를 사용한다.
## 관련 문서
- [analysis/2026-06-16-project-analysis.md](./analysis/2026-06-16-project-analysis.md) — 이 유틸을 "운영 도구(실DB 필요·CI 불가, surefire 제외)" 로 분류한 D3 테스트 분석 참조.

View File

@ -1,18 +0,0 @@
# Development
개발 워크플로, 테스트, 운영성 메모를 보관한다.
## 기본 워크플로
1. 변경 전 `git status --short`로 사용자 변경을 확인한다.
2. 분석 작업은 `rg`와 라인 번호 근거를 남긴다.
3. 보안 변경은 최소 회귀 테스트를 추가한다.
4. 문서-only 작업은 `src/``pom.xml`을 수정하지 않는다.
5. 변경 후 `git diff --name-only`로 범위를 확인한다.
## 관련 문서
- [DB 업데이트 쿼리 생성기](../db-update-query-generator.md)
- [회원가입 정보 구조](../user-signup-schema.md)
- [2026-06-16 프로젝트 전면 분석](../analysis/2026-06-16-project-analysis.md)
- [보안 개선 체크리스트](../security/security-remediation-checklist.md)

View File

@ -0,0 +1,67 @@
# 문서 카테고리 분류 기준
## 목적
`changes/` 남용을 줄이고, 문서의 주된 목적에 따라 카테고리를 일관되게 선택하기 위한 기준이다.
> 프로젝트 성격에 따라 아래 카테고리 목록에서 해당 없는 것은 삭제하고, 필요한 카테고리는 추가한다. 예: 웹 UI 가 있는 프로젝트는 `uiux-qa-report/` 를 추가할 수 있다.
## 핵심 원칙
- 카테고리는 "무엇이 바뀌었는가" 보다 **"이 문서를 나중에 왜 다시 찾는가"** 를 기준으로 선택한다.
- 한 문서에는 하나의 주 목적만 둔다.
- 문서가 두 목적을 동시에 가지면, 오래 참조할 기준 문서를 우선 만들고 필요할 때만 보조 문서를 추가한다.
## 카테고리 선택 기준
| 카테고리 | 이럴 때 사용 | 이럴 때는 사용하지 않음 |
| --- | --- | --- |
| `adr/` | 기술 선택/아키텍처 원칙에 대한 **불변 결정 레코드**. ORM/프레임워크/배포 방식/레이어 경계 등 되돌리기 어려운 결정 | 단건 구현 변경, 일회성 분석 |
| `analysis/` | 코드/데이터 흐름/성능/리스크를 이해하거나 원인을 추적하는 문서 | 수정 결과 changelog, 장기 운영 기준, 계약 스펙 |
| `architecture/` | 시스템 경계, 레이어 규칙, 저장 구조, 데이터 흐름, 스케줄러 설계처럼 오래 유지될 구조 설명 | 단건 변경 이력, 일회성 조사, ADR 에 가까운 결정(→ `adr/`) |
| `backlog/` | 미채택 자동화/기능 아이디어, 재검토 트리거 조건 기록. 구현되지 않았지만 흔적 남길 가치 있는 것 | 확정된 로드맵, 완료된 작업 기록 |
| `changes/` | 이미 반영된 구현 변경의 이력(changelog). 런타임 동작·스케줄러 룰·DB 스키마·외부 클라이언트 계약이 실제로 바뀐 경우 | 분석만 한 경우, 문서만 동기화한 경우, 재사용 가이드/절차, 장애 조사만 있고 수정이 없는 경우 |
| `contracts/` | 외부/내부 **계약 기준 문서**. HTTP 엔드포인트 계약, CLI/커맨드 스펙, 내부 모듈 간 인터페이스 | 특정 날짜의 변경 이력, 조사 메모 |
| `development/` | 반복해서 재사용할 개발 규칙, 절차, 워크플로우, 툴 운영 기준. 이 문서 자체도 여기 속함 | 특정 구현 변경 이력, 단건 장애 보고서 |
| `domain/` | 프로젝트가 다루는 **도메인 지식** (업계 규칙, 용어 정의, 비즈니스 룰 등) | 구현 변경 이력, 내부 구조 |
| `issues/` | 운영 이슈, 장애, 재현 조건, 영향 범위, 원인, 대응 내역 | 일반 기능 변경 기록, 장기 가이드 |
| `maintenance/` | 운영자가 따라야 하는 점검, 수동 조치, 배치, 데이터 정리, 유지보수 절차 | 개발 규칙, 분석 보고서 |
| `security/` | 인증/인가, 입력 검증, 비밀 값 관리, 보안 정책/패턴을 설명하는 기준 문서 | 특정 기능 수정 이력 자체 |
| `usage/` | 이식자·사용자 관점 운영 가이드, FAQ, 체크리스트 | 내부 구조 설명(→ `architecture/`), 개발 규칙(→ `development/`) |
| `work-log/` | 작업 중간 기록, 세션 간 handoff 메모, 시점성 있는 진행 로그 | 최종 기준 문서, 장기 참조용 설명 |
| `graph/` | **자동 생성** (graphify). 사람이 손으로 편집하지 않음. `index.md` 만 메타로 커밋 | 사람이 작성하는 아키텍처 설명(→ `architecture/`) |
## `changes/` 를 써도 되는 경우
- 코드가 실제로 수정되었다.
- 또는 코드가 소비하는 설정/정적 데이터가 바뀌어 실제 동작이 달라졌다.
- 문서의 중심이 "현재 구조 설명" 이 아니라 "이번 작업에서 무엇이 왜 바뀌었는지" 다.
- 읽는 사람이 "언제 어떤 구현 변화가 들어갔는지" 를 확인하려고 이 문서를 찾는다.
## `changes/` 로 보내면 안 되는 경우
- 구현은 그대로고 설명만 보강했다.
- README / CLAUDE.md 와 docs/ 를 동기화했다.
- 재사용 가능한 작업 절차나 작성 규칙을 정리했다(→ `development/`).
- 버그 원인/구조를 분석했지만 수정은 아직 없다(→ `analysis/`).
- 운영 장애를 복기하고 재현/영향 범위를 남기는 것이 핵심이다(→ `issues/`).
## 빠른 결정 순서
1. 기술 선택/아키텍처 원칙에 대한 되돌리기 어려운 결정인가? → `adr/`.
2. 운영 장애나 이슈 대응 기록인가? → `issues/`.
3. 프로젝트의 업무 도메인 지식인가? → `domain/`.
4. 외부/내부 계약 스펙인가? → `contracts/`.
5. 현재 기준의 구조/가이드를 설명하는 문서인가? → `architecture/`, `development/`, `security/` 중 하나.
6. 조사, 비교, 원인 추적, 리스크 파악이 핵심인가? → `analysis/`.
7. 수동 운영 절차나 유지보수 실행 가이드인가? → `maintenance/`.
8. 진행 중인 작업 메모인가? → `work-log/`.
9. 미채택 아이디어/흔적인가? → `backlog/`.
10. 위가 아니고 실제 구현 변경 이력을 남기는 문서인가? 그때만 → `changes/`.
## 함께 작성하는 규칙
- 구현 변경과 장기 기준 문서가 동시에 필요하면, 기준 문서는 해당 도메인 카테고리(`contracts/`, `architecture/`, `domain/` 등) 에 두고 `changes/` 에는 변경 요약만 남긴다.
- 문서 동기화만 했다면 원칙적으로 `changes/` 를 만들지 말고, 대상 기준 문서를 직접 갱신한다.
- 장애 수정이 있었더라도 핵심 가치가 장애 원인과 대응 이력 보존이라면 `issues/` 를 우선하고, 필요할 때만 관련 `changes/` 를 추가한다.
- ADR 에 해당하는 결정이면 `changes/` 대신 `adr/` 에 불변 레코드로 남긴다.

10
docs/development/index.md Normal file
View File

@ -0,0 +1,10 @@
# Development — 개발 규칙 / 절차 / 툴 운영
반복해서 재사용할 규칙/워크플로우/툴 사용법을 둔다. 이 카테고리에 문서를 추가할 때는 `./document-category-classification.md` 의 분류 기준을 따른다.
## 목록
- [verification-strategies.md](./verification-strategies.md) — `verification-advisor` 가 읽는 검증 전략 레지스트리 (프로젝트별 `cmd` 를 채워 사용)
- [document-category-classification.md](./document-category-classification.md) — 카테고리 분류 기준 (불필요한 카테고리는 프로젝트에 맞게 정리)
> atp 플러그인 번들 레퍼런스(`agent-team-protocol.md`, `agent-catalog.md`, `documentation-guidelines.md`, `search-tool-matrix.md`)는 플러그인 캐시에 있으며 이 프로젝트로 복사되지 않는다. 에이전트가 `${CLAUDE_PLUGIN_ROOT}/docs/...` 로 직접 참조한다.

View File

@ -0,0 +1,117 @@
# Verification Strategies Registry
`verification-advisor` 가 읽는 단일 레지스트리. 이 파일에 등록된 전략만 검증 대상이 된다. 전략 추가/수정 시 이 파일만 변경하면 에이전트 구조(tier) 는 건드릴 필요 없다.
## 검증 사다리 (L1 / L2 / L3)
프로젝트의 검증은 3단으로 나뉜다. 코드 변경의 성격에 따라 **적용할 최소 L 레벨** 이 결정된다.
| 레벨 | 대상 | 신뢰 범위 |
|---|---|---|
| **L1** | 단위·회귀 (타입체크 + 단위/회귀 테스트) | "내가 고친 라인이 깨지지 않음" + "과거 버그가 재발하지 않음" |
| **L2** | 원격/외부 의존 계약 (live contract 테스트) | "외부 서비스 스키마·필드가 우리 기대와 정합" |
| **L3** | End-to-end (실제 런타임·DB·외부 서비스 전부 도는 시나리오) | "사용자 시나리오가 실제 스택에서 끝까지 동작" |
### 버그 범주 → 적용 L 레벨 (예시 · 프로젝트 맞춤)
프로젝트 특성에 맞게 아래 표를 채운다.
| 버그/변경 범주 | 의무 레벨 |
|---|---|
| 외부 API 스키마/응답 파싱 수정 | L1 + L2 |
| 외부 서비스 설정 상수 변경 | L1 + L2 |
| 인증/인가/롤 플로우 수정 | L1 + L2 + L3 |
| 순수 도메인 로직 (외부 의존 없음) | L1 |
| 인프라 설정 (container/env/compose) | L1 + 수동 스모크 |
**회귀 테스트 의무**: 버그 수정 커밋은 해당 버그를 재현하는 테스트를 같이 포함한다. revert 시 테스트가 실패하고, 수정 후엔 통과해야 한다.
### 실행 수단
프로젝트 루트에 통합 검증 스크립트를 둘 것을 권장한다 (예: `scripts/verify.sh`, `make verify`, `cargo xtask verify`). 스크립트는 L1 → L2 → 로그 스캔 순차 실행을 담당.
- L2 를 조건부로 비활성화하는 환경 변수를 제공 (원격 꺼진 CI 대비). 예: `SKIP_LIVE_CONTRACT=1`.
- 원격 테스트의 seed/live URL 미지정 시 L2 는 자동 skip 처리되도록 구성.
## 사용 규약
- `verification-advisor` 는 호출 시 변경 scope 를 받아 매칭되는 전략만 실행한다.
- 전략 개수에 따라 tier 가 자동 결정된다:
- 1개 → advisor 가 직접 실행 (tier 2)
- 2개 이상 → `worker:` 필드가 있는 전략은 해당 worker spawn (tier 3), 나머지는 advisor 순차 실행
- 신규 worker 도입은 [agent-team-protocol.md §9](./agent-team-protocol.md) 의 확장 트리거 참조.
### 집합 전수 AC 실행 지침
design.md 의 "검증 포인트" 에 다음 형식의 AC 가 있으면 "집합 전수 체크" 유형으로 분류한다.
```
AC-N: <집합명> 전수 N건 ... (grep -c ... == N)
```
실행 방법:
1. AC 에 명시된 `grep -c` 명령을 그대로 실행한다.
2. 반환값이 AC 에 명시된 기대 수와 다르면 **blocker FAIL** 로 판정한다.
3. 실패 시 출력: 실제 매치 수 + 누락된 항목을 diff 또는 목록으로 제시.
집합 전수 AC 는 특성상 "부분 통과"가 없다 — 기대 수에서 1 이라도 어긋나면 전체 FAIL. 규약 근거는 프로토콜 §4.3 + `design-advisor.md` "집합 전수 체크 AC 패턴" 섹션.
## 전략 (템플릿)
아래는 YAML 스켈레톤이다. 프로젝트 기술 스택에 맞게 `cmd` 를 교체한다 (`pnpm` / `yarn` / `npm` / `cargo` / `go test` / `pytest` / `mvn test` 등).
```yaml
strategies:
# ── L1 ────────────────────────────────────────
- id: typecheck
cmd: <프로젝트 타입체크 명령> # e.g. pnpm typecheck / cargo check / mypy .
scope: all
timeout_s: 180
failure_severity: blocker
- id: unit
cmd: <프로젝트 단위 테스트 명령> # e.g. pnpm test / cargo test / pytest
scope: <src glob> # e.g. src/**
timeout_s: 600
failure_severity: blocker
# ── L2 ────────────────────────────────────────
- id: contract-<external>
cmd: <계약 테스트 명령>
scope:
- <외부 의존이 집중된 경로>
timeout_s: 60
failure_severity: blocker
preconditions:
- "<외부 서비스 기동 확인 방법>"
- "<필요 환경 변수>"
note: |
원격 미기동 또는 seed 미지정 시 runtime 에서 skip 처리.
blocker 로 두는 이유는 "원격이 떠 있는데 계약 위반" 이 사용자 장애로 직결되기 때문.
# ── 통합 ──────────────────────────────────────
- id: verify-all
cmd: <통합 검증 스크립트> # e.g. pnpm verify / make verify
scope: all
timeout_s: 900
failure_severity: blocker
note: "L1 + L2 + 로그 스캔 통합. ATP task 세션 종료 전 의무."
```
## 필드 정의
| 필드 | 의미 |
|---|---|
| `id` | 전략 식별자 (unique) |
| `cmd` | 실행 명령 |
| `scope` | glob. 이 패턴에 변경이 걸칠 때만 실행 |
| `timeout_s` | 초 단위 타임아웃 |
| `failure_severity` | `blocker` (실패 시 전체 검증 실패) \| `warning` (기록만) |
| `worker` (optional) | spawn 할 worker 이름. 미지정 시 advisor 직접 실행 |
## 확장 시
- 새 전략 추가: 위 YAML 블록에 항목 추가.
- Worker 분리가 필요한 수준에 도달 (브라우저 테스트, 장시간 E2E 등): `worker:` 필드 붙이고 해당 worker 파일 신설 + `verification-advisor.md` 의 tools 에 `Agent` 추가.
- 기준은 [agent-team-protocol.md §9 확장 트리거 레지스트리](./agent-team-protocol.md#9-확장-트리거-레지스트리).

7
docs/domain/index.md Normal file
View File

@ -0,0 +1,7 @@
# Domain — 도메인 지식
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

7
docs/feedback/index.md Normal file
View File

@ -0,0 +1,7 @@
# Feedback — 검토·수정 요청 캡처 inbox
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

6
docs/graph/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# graphify 산출물 본체는 재생성 가능하므로 커밋하지 않는다.
# index.md 와 본 .gitignore 만 커밋 대상.
*
!index.md
!.gitignore

48
docs/graph/index.md Normal file
View File

@ -0,0 +1,48 @@
---
kind: graphify-meta
last_generated_at: 2026-06-16T13:02:01+0900
source_commit: 33a4465
scopes:
- src
- docs
---
# Graph — graphify 산출물 메타
이 디렉토리는 `/graphify` 가 생성하는 지식 그래프 산출물의 **메타 정보만** 커밋한다. HTML/JSON/audit 본체는 `.gitignore` 대상이며 재생성 가능하다.
> 설치·적용 가이드는 `atp-graphify` add-on 번들의 `graphify-usage.md` 참조.
## 엔트리포인트 사용법
- 구조 탐색이 필요한 작업 전에 이 파일의 `last_generated_at``source_commit` 을 확인한다.
- 메인 브랜치 기준 마지막 소스 커밋보다 뒤쳐져 있다면 `graph-refresh-checker` 서브에이전트를 호출해 staleness 판정을 받는다.
- 판정 결과가 `partial-stale` / `fully-stale` 이면 메인 에이전트가 `/graphify` 를 호출해 재생성하고, 이 파일을 갱신한다.
- 더 이상 관련 없는 scope (제거된 모듈 등) 의 산출물은 재생성 전 **삭제** 한다.
## 산출물 레이아웃
```
docs/graph/
├── index.md # 이 파일 — 메타, 커밋 대상
├── .gitignore # 본체 무시
└── <scope>/ # scope 별 산출물 디렉토리 (무시됨)
├── graph.html
├── graph.json
└── audit.md
```
scope 예시: `src`, `src-features`, `docs`, `full` 등. 한 번에 여러 scope 를 운용할 수 있다.
## Scopes
| scope | 마지막 생성 | 소스 커밋 | 대상 경로 | 요약 |
| --- | --- | --- | --- | --- |
| `src` | 2026-06-16 | `33a4465` | `src/` (Java 31 + AST) | 384 노드 / 613 엣지 / 23 커뮤니티. Spring MVC 컨트롤러→매퍼→data 흐름, 모집·게임·인증 도메인 군집. 정적 이미지 제외. |
| `docs` | 2026-06-16 | `33a4465` | `docs/` (md 21 + DDL 2, 자기 산출물 제외) | 44 노드 / 56 엣지 / 7 커뮤니티. 문서 카테고리 체계 + DB 스키마/도메인 + 검증·ATP·ADR 정책 군집. |
## 갱신 시 체크리스트
- [ ] frontmatter 의 `last_generated_at`, `source_commit`, `scopes` 갱신
- [ ] 아래 "Scopes" 표에 한 줄 추가/갱신
- [ ] 폐기된 scope 가 있다면 해당 디렉토리 `rm -rf` + 표에서 제거

View File

@ -1,41 +1,43 @@
# bibimbap Documentation
# 문서 인덱스
이 디렉터리는 프로젝트 분석, 보안 개선 체크리스트, 아키텍처 메모, 개발 운영 문서를 모아 둔다.
**docs-first 워크플로우**: 어떤 작업을 시작하기 전이든 이 파일을 먼저 읽고, 해당 카테고리의 `index.md` 를 거쳐 관련 문서를 확인한 뒤 구현에 착수한다.
## 빠른 링크
> 작성/갱신 규칙과 카테고리 분류 기준은 `development/documentation-guidelines.md` + `development/document-category-classification.md` 를 따른다.
- [2026-06-16 프로젝트 전면 분석](analysis/2026-06-16-project-analysis.md)
- [보안 개선 체크리스트](security/security-remediation-checklist.md)
- [DB 업데이트 쿼리 생성기](db-update-query-generator.md)
- [회원가입 정보 구조](user-signup-schema.md)
## 카테고리
## 문서 트리
| 경로 | 용도 | 인덱스 |
| --- | --- | --- |
| [adr/](./adr/index.md) | 되돌리기 어려운 아키텍처/기술 결정 (불변, 번호제) | adr/index.md |
| [analysis/](./analysis/index.md) | 코드/흐름/성능/리스크 분석 | analysis/index.md |
| [architecture/](./architecture/index.md) | 시스템 경계, 레이어 규칙, 저장 구조 | architecture/index.md |
| [backlog/](./backlog/index.md) | 미채택 아이디어, 재검토 트리거 | backlog/index.md |
| [changes/](./changes/index.md) | 실제 동작이 바뀐 구현 변경 이력 | changes/index.md |
| [contracts/](./contracts/index.md) | 외부/내부 계약 스펙 | contracts/index.md |
| [development/](./development/index.md) | 재사용 가능한 개발 규칙/절차/툴 운영 | development/index.md |
| [domain/](./domain/index.md) | 도메인 지식 (프로젝트가 다루는 업무 영역의 규칙·개념) | domain/index.md |
| [issues/](./issues/index.md) | 운영 장애/이슈 대응 기록 | issues/index.md |
| [maintenance/](./maintenance/index.md) | 수동 운영 절차 (마이그레이션/복구/배치) | maintenance/index.md |
| [security/](./security/index.md) | 인증/인가, 입력 검증, 비밀 값 관리 | security/index.md |
| [usage/](./usage/index.md) | 이식자용 사용 가이드·FAQ·체크리스트 | usage/index.md |
| [work-log/](./work-log/index.md) | 세션 간 handoff, 시점성 있는 작업 메모 | work-log/index.md |
| [feedback/](./feedback/index.md) | 검토·수정 요청 캡처 inbox (선택) | feedback/index.md |
| [graph/](./graph/index.md) | **자동 생성** — graphify 산출물 메타 | graph/index.md |
```text
docs/
index.md
adr/
README.md
analysis/
README.md
2026-06-16-project-analysis.md
architecture/
README.md
development/
README.md
security/
README.md
security-remediation-checklist.md
db-update-query-generator.md
user-signup-schema.md
security-hardening-ddl.sql
recruit-posts-ddl.sql
profile-management-prompt.md
```
> 프로젝트 성격에 따라 불필요한 카테고리는 제거하거나 새 카테고리를 추가한다. 기준은 `development/document-category-classification.md`.
## 운영 원칙
## 빠른 참조
- 프로젝트 분석 문서는 발견마다 `file:line` 근거를 남긴다.
- 코드 변경 없이 분석만 하는 작업은 `src/``pom.xml`을 수정하지 않는다.
- 보안 개선은 체크리스트의 우선순위와 완료 조건을 기준으로 별도 PR로 분리한다.
- 스키마, 운영 쿼리, 배포 절차를 바꾸는 문서는 관련 보안/아키텍처 문서와 교차 링크한다.
- **아키텍처 / 구조 파악**: `architecture/` → 부족하면 `graph/index.md` 확인 후 필요 시 `/graphify` 호출
- **이식 후 설정**: `usage/setup-checklist.md` → 문제 발생 시 `usage/faq.md`
- **기술 선택 근거**: `adr/`
- **외부/내부 계약 스펙**: `contracts/`
- **도메인 규칙**: `domain/`
- **장애 이력**: `issues/`
## 작업 시작 체크리스트
- [ ] 이 인덱스에서 관련 카테고리를 식별했는가?
- [ ] 해당 카테고리 `index.md` 를 읽었는가?
- [ ] 구조 탐색이 필요한 작업이라면 `graph/index.md``last_generated_at` 이 최근인지 확인했는가?
- [ ] 작업 결과로 새 문서가 필요한가? (분류 기준은 `development/document-category-classification.md`)

7
docs/issues/index.md Normal file
View File

@ -0,0 +1,7 @@
# Issues — 운영 장애/이슈 대응 기록
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

View File

@ -0,0 +1,7 @@
# Maintenance — 수동 운영 절차 (마이그레이션/복구/배치)
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

View File

@ -1,17 +0,0 @@
# Security
보안 분석 결과와 개선 체크리스트를 보관한다.
## 문서
- [보안 개선 체크리스트](security-remediation-checklist.md)
- [2026-06-16 프로젝트 전면 분석](../analysis/2026-06-16-project-analysis.md)
- [Security hardening DDL](../security-hardening-ddl.sql)
## 원칙
- 상태 변경 요청은 CSRF 검증을 기본값으로 둔다.
- DB 접근은 MyBatis `#{}` 바인딩을 사용하고 `${}` 동적 치환은 금지한다.
- 업로드 파일은 크기, 타입, 경로 boundary를 모두 검증한다.
- 사용자 입력 출력은 JSP에서 escape하거나 클라이언트에서 `textContent`로 렌더링한다.
- 보안 변경은 최소 하나 이상의 회귀 테스트를 함께 추가한다.

7
docs/security/index.md Normal file
View File

@ -0,0 +1,7 @@
# Security — 인증/인가, 입력 검증, 비밀 값 관리
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_

23
docs/usage/index.md Normal file
View File

@ -0,0 +1,23 @@
---
kind: usage
title: Usage 카테고리 인덱스
description: 이식자·운영자 대상 사용 가이드(설정 체크리스트·FAQ 등) 인덱스.
owner: template-maintainer
stability: living
last_reviewed: 2026-05-07
---
# Usage — 이식자 사용 가이드
이 카테고리는 본 템플릿을 cp-R 로 **이식한 사용자 관점** 의 운영 문서를 모은다. 내부 구조 설명은 `architecture/`, 개발 규칙은 `development/` 로 분리.
## 목록
- [setup-checklist.md](./setup-checklist.md) — 이식 후 30분 내 완료 설정 체크리스트 (복사 제외 재확인 → CLAUDE.md 병합 → 검증 명령 교체 → 카테고리 조정 → graphify 도입 여부)
- [faq.md](./faq.md) — 문제 해결 / FAQ + 이식자 실수 카탈로그 (M1~M8)
## 먼저 읽을 순서
1. `../../README.md` §3 (설치) — cp-R 기본 절차
2. [setup-checklist.md](./setup-checklist.md) — 복사 직후 30분 내 정리
3. 문제 발생 시 [faq.md](./faq.md)

View File

@ -188,3 +188,7 @@ Content-Type: application/json
```
실제 구현에서는 클라이언트가 넘긴 `providerUserId`를 그대로 신뢰하기보다, 가능하면 서버에서 소셜 로그인 토큰을 검증한 뒤 provider 사용자 ID를 확정하는 방식을 권장한다.
## 관련 문서
- [analysis/2026-06-16-project-analysis.md](./analysis/2026-06-16-project-analysis.md) — D4 데이터 모델 분석. 현재 코드는 `provider='email'` 만 구현(소셜 확장은 `추정`)이라는 현황 확인 참조.

7
docs/work-log/index.md Normal file
View File

@ -0,0 +1,7 @@
# Work Log — 세션 간 handoff, 시점성 작업 메모
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
## 목록
_(아직 문서 없음)_