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:
commit
8c41b228c9
|
|
@ -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.
|
|
||||||
|
|
@ -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 한 문서에 통합).
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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 조회 권장.
|
||||||
|
|
@ -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: []
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
/cache
|
||||||
|
/project.local.yml
|
||||||
|
|
@ -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`.
|
||||||
|
|
@ -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`.
|
||||||
|
|
@ -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 + 표에서 제거.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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 와 동일 — 생략.
|
||||||
|
|
@ -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 기동 전제. 미충족 시 통합 동작은 수동 검증 필요.
|
||||||
|
|
@ -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` 설정 존재.
|
||||||
|
|
@ -1,18 +1,133 @@
|
||||||
project_name: bibimbap
|
# the name by which the project can be referenced within Serena
|
||||||
language: java
|
project_name: "bibimbap"
|
||||||
build_system: maven
|
|
||||||
frameworks:
|
|
||||||
- Spring Boot
|
# list of languages for which language servers are started; choose from:
|
||||||
- JSP
|
# al angular ansible bash clojure
|
||||||
- MyBatis
|
# cpp cpp_ccls crystal csharp csharp_omnisharp
|
||||||
package_type: war
|
# dart elixir elm erlang fortran
|
||||||
docs:
|
# fsharp go groovy haskell haxe
|
||||||
index: docs/index.md
|
# hlsl html java json julia
|
||||||
latest_analysis: docs/analysis/2026-06-16-project-analysis.md
|
# kotlin lean4 lua luau markdown
|
||||||
security_checklist: docs/security/security-remediation-checklist.md
|
# matlab msl nix ocaml pascal
|
||||||
guidelines:
|
# perl php php_phpactor powershell python
|
||||||
- For documentation-only analysis, do not modify src/ or pom.xml.
|
# python_jedi python_ty r rego ruby
|
||||||
- Record security findings with file:line evidence.
|
# ruby_solargraph rust scala scss solidity
|
||||||
- Prefer rg for code and document search.
|
# svelte swift systemverilog terraform toml
|
||||||
- Preserve user changes in the working tree.
|
# typescript typescript_vts vue yaml zig
|
||||||
- Split security remediation into small PRs with tests.
|
# (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 read‑only.
|
||||||
|
# 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: []
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,12 @@
|
||||||
- 문서 인덱스: `docs/index.md`
|
- 문서 인덱스: `docs/index.md`
|
||||||
- 최신 프로젝트 분석: `docs/analysis/2026-06-16-project-analysis.md`
|
- 최신 프로젝트 분석: `docs/analysis/2026-06-16-project-analysis.md`
|
||||||
- 보안 개선 체크리스트: `docs/security/security-remediation-checklist.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 -->
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# ADR
|
|
||||||
|
|
||||||
Architecture Decision Record를 보관한다.
|
|
||||||
|
|
||||||
## 작성 템플릿
|
|
||||||
|
|
||||||
```md
|
|
||||||
# ADR-NNN: 제목
|
|
||||||
|
|
||||||
## 상태
|
|
||||||
|
|
||||||
Proposed | Accepted | Superseded
|
|
||||||
|
|
||||||
## 배경
|
|
||||||
|
|
||||||
결정이 필요한 이유와 관련 근거를 적는다.
|
|
||||||
|
|
||||||
## 결정
|
|
||||||
|
|
||||||
선택한 방향을 적는다.
|
|
||||||
|
|
||||||
## 결과
|
|
||||||
|
|
||||||
장점, 단점, 후속 작업을 적는다.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 후보
|
|
||||||
|
|
||||||
- ADR-001: WebGL asset 제공 origin 정책
|
|
||||||
- ADR-002: 서비스 레이어 도입 범위
|
|
||||||
- ADR-003: 좋아요/댓글 사용자 식별 정책
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
# ADR 인덱스
|
||||||
|
|
||||||
|
**카테고리 용도**: 기술 선택·아키텍처 원칙에 대한 되돌리기 어려운 결정. 불변(append-only). 결정을 뒤집을 때는 기존 ADR 을 수정하지 않고 새 ADR 을 발행하여 "supersedes ADR-NNNN" 명시.
|
||||||
|
|
||||||
|
파일명 규칙: `ADR-NNNN-kebab-case-title.md` (NNNN 은 0001 부터 순차 증가)
|
||||||
|
|
||||||
|
## 결정 목록
|
||||||
|
|
||||||
|
| ADR | 제목 | 상태 | 날짜 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| *(아직 등록된 ADR 없음)* | | | |
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
- 목적: 현재 프로젝트의 아키텍처, 보안, 품질, 도메인 상태를 읽기 전용으로 분석하고 후속 보안 개선 항목을 정리한다.
|
> 분석 방식: 30개 Java 파일 + 16개 JSP + 6개 MyBatis 매퍼 전수 직접 읽기(Read/Grep/Bash). 외부 자료 미사용 → 모든 사실은 코드 직접 확인(`확인됨`). 추정 항목은 `추정`/`미확인` 으로 명시했으며 사실로 격상하지 않았다.
|
||||||
- 변경 원칙: 이 분석 세션은 문서와 프로젝트 지침만 추가/수정한다. `src/`와 `pom.xml`은 수정하지 않는다.
|
>
|
||||||
- 조사 방식: 파일 목록, 컨트롤러, 매퍼, JSP, 설정 파일을 `rg`와 라인 번호 기준으로 확인했다.
|
> 출처: work-session `20260616-111711` 의 `research/analysis.md` 를 정제한 정본 기록.
|
||||||
|
|
||||||
## 요약
|
## 요약
|
||||||
|
|
||||||
- 보안 기초는 비교적 견고하다. MyBatis 매퍼는 동적 `${}` 조립 없이 `#{}` 바인딩을 쓰고, 비밀번호는 PBKDF2-SHA256 210,000회 반복으로 저장한다.
|
Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read-only 분석. 4개 차원으로 정리한다: 아키텍처(D1), 보안(D2, 최우선), 코드품질/기술부채(D3), 도메인/기능(D4).
|
||||||
- 주요 보안 갭은 `POST /login`, `POST /signup`에 CSRF 검증이 없는 점이다. 다른 주요 상태 변경 API는 `CsrfTokens.isValid`를 적용하고 있다.
|
|
||||||
- 파일 업로드와 WebGL asset 제공 경로는 normalize/startsWith 검사를 반복 적용하고, zip-slip 및 XSS 방어도 일관된 편이다.
|
|
||||||
- 구조적 부채는 서비스 레이어 부재, fat controller, 프로토타입 잔재(`abstracts`, `header.jspf`, `GameCatalog`), 실질 테스트 부족이다.
|
|
||||||
- 좋아요/댓글은 DB 매퍼와 삭제 연계는 있으나, 상세 화면은 `localStorage`만 사용해 서버 영속화가 미완성이다.
|
|
||||||
|
|
||||||
## 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`
|
| 1 | INFO(긍정) | **SQL 인젝션 표면 0** — 6개 매퍼 전 쿼리가 파라미터 바인딩 `#{}` 만 사용. `${}` 문자열 보간 단 1건도 없음(java/xml 전수 grep). 검색어도 `ILIKE CONCAT('%', #{query}, '%')` 로 안전. | mapper/*.java 전체 |
|
||||||
- 게임 등록/수정/삭제도 컨트롤러가 검증, 권한 확인, 매퍼 호출을 직접 수행한다. 근거: `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`
|
| 2 | INFO(긍정) | **비밀번호 해싱 견고** — PBKDF2WithHmacSHA256, 210,000 iterations, 16B salt, 256bit, `MessageDigest.isEqual`(상수시간 비교). 형식 `pbkdf2_sha256$iter$salt$hash`. | UserController.java:542-576 |
|
||||||
- 모집글 등록도 동일 패턴이다. 근거: `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`
|
| 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`
|
### 패키지별 책임 (1줄)
|
||||||
- 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`
|
|
||||||
|
|
||||||
영향: 업로드 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`
|
- **페이지 렌더**: Controller → (서비스 레이어 없음) → Mapper → DB → ModelAndView/Model → `/WEB-INF/views/*.jsp` (ViewResolver prefix/suffix 는 application.properties).
|
||||||
- `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`
|
- **API mutation**: `@PostMapping`/`@DeleteMapping` → CSRF 검증 → 세션 인증 → 입력 검증 → Mapper → JSON ResponseEntity.
|
||||||
- `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`
|
- **서비스 레이어 부재**(확인됨): 컨트롤러가 매퍼를 직접 주입·호출. `@Transactional` 은 컨트롤러 메서드에 부착. → requirements 가 "service 레이어 존재" 를 전제했다면 충돌(아래 종합 판단 참조).
|
||||||
- `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`
|
- **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 동적 치환 `${}` 사용은 발견되지 않았다.
|
- `@SpringBootApplication` 단일. MyBatis 는 `mybatis-spring-boot-starter` 자동 배선 + `@Mapper` 스캔(별도 @MapperScan 불필요, 확인됨).
|
||||||
- 검색 쿼리도 `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`
|
- 프로파일: 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`).
|
||||||
- 주요 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`
|
- 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`
|
- 하드코딩 시크릿 **없음**(확인됨): tracked 파일은 `*.example`(placeholder `your_username/your_password`)뿐. 실제 db.properties 는 gitignore. application.properties 에 비밀값 없음.
|
||||||
- 검증은 `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`
|
- 비밀번호 저장: PBKDF2-SHA256 210k iter(상기 Top#2). 평문·약한해시 없음.
|
||||||
- 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`
|
|
||||||
|
|
||||||
판정: 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`
|
### JSP XSS / CSRF
|
||||||
- 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`
|
|
||||||
|
|
||||||
판정: 현재 방어는 좋다. 다만 운영 설정에서 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`
|
- zip-slip 방어: `target.startsWith(targetDir)` 정규화 검증(GameUploadController.java:128,200,266-269).
|
||||||
- 홈 검색어와 게임 카드 출력도 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`
|
- zip bomb 방어: entry 수 8000 / 압축해제 512MB 상한(line 40-41,262,297).
|
||||||
- 댓글 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`
|
- path traversal: 프로필 아바타·에셋 서빙 모두 `normalize()`+`startsWith(root)` 검증(UserController.java:248-263, GameAssetController.java:101-104). UUID 검증으로 게임 디렉토리 화이트리스트.
|
||||||
- 프로필 아바타 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`
|
- 콘텐츠 타입: 확장자 화이트리스트(png/jpg/webp/gif). **추정 한계**: MIME/확장자만 검사, 매직바이트 미검증 → polyglot 업로드 가능성(LOW, WebGL asset 은 nosniff+CSP 로 완화).
|
||||||
- 외부 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`
|
|
||||||
|
|
||||||
판정: 현재 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`
|
**강점**: 보안 기초가 의외로 탄탄하다 — SQLi 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관, 세션 고정 방어, 소유권 인가 일관. 코드가 "보안을 의식하고 작성" 된 흔적이 뚜렷.
|
||||||
- 현재 컨트롤러는 `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`
|
|
||||||
|
|
||||||
영향: 문서와 구현 간 목표 차이가 있다. 소셜/게스트 가입을 실제 기능으로 만들지, 문서를 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`
|
**보안 미결 결정**: login/signup CSRF 적용 여부, 세션쿠키 Secure/SameSite, spring-boot SNAPSHOT 고정 — design 단계 보안 결정 필요.
|
||||||
- 게임 수정은 작성자 확인 후 `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`
|
|
||||||
|
|
||||||
영향: 게임 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`
|
1. 게임 좋아요·댓글의 서버 영속화는 의도된 미완성인가, 폐기된 기능인가? (스키마·매퍼는 완비, 엔드포인트만 없음) — 코드만으로 의도 판별 불가(`미확인`).
|
||||||
- 댓글 매퍼는 존재한다. 근거: `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`
|
2. 세션 쿠키 Secure/SameSite·HTTPS 강제는 배포 톰캣/리버스프록시 설정에 의존 — 저장소 코드 밖이라 `미확인`. 프로덕션 설정 확인 필요.
|
||||||
- 상세 화면 좋아요는 `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`
|
3. `provider`/`provider_user_id` 컬럼 = 소셜로그인 확장 예정 스키마인지(현재 email 전용) — `추정`.
|
||||||
- 상세 화면 댓글도 `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`
|
4. spring-boot 3.5.14-SNAPSHOT 을 의도적으로 SNAPSHOT 유지하는 이유(특정 미릴리스 픽스 의존?) — `미확인`.
|
||||||
|
5. 의존성 CVE 스캔 미수행(외부 조회 안 함) — 별도 `mvn dependency-check` 또는 OSV 조회 권장.
|
||||||
|
|
||||||
영향: 사용자는 브라우저를 바꾸면 좋아요/댓글을 잃는다. 서버 연결 시 익명 허용 여부, 남용 방지, 삭제 권한, CSRF 정책을 먼저 확정해야 한다.
|
## 관련 링크 (References)
|
||||||
|
|
||||||
### D4. 모집글은 서버 영속화되어 있다
|
- 출처(work-session): `.atp/work-session/20260616-111711/research/analysis.md`
|
||||||
|
- [db-update-query-generator.md](../db-update-query-generator.md) — `DbUpdateQueryGeneratorTest` 운영 도구(D3 테스트 항목 관련)
|
||||||
- 목록, 작성, 상세 라우트가 존재한다. 근거: `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`
|
- [user-signup-schema.md](../user-signup-schema.md) — `users`/`user_auth_identities` 스키마 상세(D4 데이터 모델 관련)
|
||||||
- 작성 시 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`
|
- 카테고리 분류 기준: [development/document-category-classification.md](../development/document-category-classification.md)
|
||||||
- 매퍼는 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-의존성세션운영-하드닝)를 따른다.
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# Analysis
|
|
||||||
|
|
||||||
프로젝트 전면 분석, 감사 기록, 리스크 인벤토리를 보관한다.
|
|
||||||
|
|
||||||
## 문서
|
|
||||||
|
|
||||||
- [2026-06-16 프로젝트 전면 분석](2026-06-16-project-analysis.md)
|
|
||||||
|
|
||||||
## 작성 규칙
|
|
||||||
|
|
||||||
- 분석 범위와 변경 범위를 먼저 적는다.
|
|
||||||
- 발견 사항은 아키텍처, 보안, 품질, 도메인 축으로 분류한다.
|
|
||||||
- 모든 발견은 `file:line` 근거를 포함한다.
|
|
||||||
- 후속 작업은 보안/기능/리팩터링 PR로 분리할 수 있게 체크리스트와 연결한다.
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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 범위
|
|
||||||
|
|
@ -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/`
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Backlog — 미채택 아이디어, 재검토 트리거
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Changes — 실제 동작이 바뀐 구현 변경 이력
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Contracts — 외부/내부 계약 스펙
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -102,3 +102,7 @@ $env:JAVA_HOME='C:\Users\acst0\.jdks\azul-21.0.10'
|
||||||
- 따라서 SQL을 실행할 때는 반드시 `live` 스키마가 기본 스키마로 잡힌 연결에서 실행해야 한다.
|
- 따라서 SQL을 실행할 때는 반드시 `live` 스키마가 기본 스키마로 잡힌 연결에서 실행해야 한다.
|
||||||
- `dev` DB 접속 정보는 `src/main/resources/dev/db.properties`를 사용한다.
|
- `dev` DB 접속 정보는 `src/main/resources/dev/db.properties`를 사용한다.
|
||||||
- `live` DB 접속 정보는 `src/main/resources/live/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 테스트 분석 참조.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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/` 에 불변 레코드로 남긴다.
|
||||||
|
|
@ -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/...` 로 직접 참조한다.
|
||||||
|
|
@ -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-확장-트리거-레지스트리).
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Domain — 도메인 지식
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Feedback — 검토·수정 요청 캡처 inbox
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
# graphify 산출물 본체는 재생성 가능하므로 커밋하지 않는다.
|
||||||
|
# index.md 와 본 .gitignore 만 커밋 대상.
|
||||||
|
|
||||||
|
*
|
||||||
|
!index.md
|
||||||
|
!.gitignore
|
||||||
|
|
@ -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` + 표에서 제거
|
||||||
|
|
@ -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
|
> 프로젝트 성격에 따라 불필요한 카테고리는 제거하거나 새 카테고리를 추가한다. 기준은 `development/document-category-classification.md`.
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
## 운영 원칙
|
## 빠른 참조
|
||||||
|
|
||||||
- 프로젝트 분석 문서는 발견마다 `file:line` 근거를 남긴다.
|
- **아키텍처 / 구조 파악**: `architecture/` → 부족하면 `graph/index.md` 확인 후 필요 시 `/graphify` 호출
|
||||||
- 코드 변경 없이 분석만 하는 작업은 `src/`와 `pom.xml`을 수정하지 않는다.
|
- **이식 후 설정**: `usage/setup-checklist.md` → 문제 발생 시 `usage/faq.md`
|
||||||
- 보안 개선은 체크리스트의 우선순위와 완료 조건을 기준으로 별도 PR로 분리한다.
|
- **기술 선택 근거**: `adr/`
|
||||||
- 스키마, 운영 쿼리, 배포 절차를 바꾸는 문서는 관련 보안/아키텍처 문서와 교차 링크한다.
|
- **외부/내부 계약 스펙**: `contracts/`
|
||||||
|
- **도메인 규칙**: `domain/`
|
||||||
|
- **장애 이력**: `issues/`
|
||||||
|
|
||||||
|
## 작업 시작 체크리스트
|
||||||
|
|
||||||
|
- [ ] 이 인덱스에서 관련 카테고리를 식별했는가?
|
||||||
|
- [ ] 해당 카테고리 `index.md` 를 읽었는가?
|
||||||
|
- [ ] 구조 탐색이 필요한 작업이라면 `graph/index.md` 의 `last_generated_at` 이 최근인지 확인했는가?
|
||||||
|
- [ ] 작업 결과로 새 문서가 필요한가? (분류 기준은 `development/document-category-classification.md`)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Issues — 운영 장애/이슈 대응 기록
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Maintenance — 수동 운영 절차 (마이그레이션/복구/배치)
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -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`로 렌더링한다.
|
|
||||||
- 보안 변경은 최소 하나 이상의 회귀 테스트를 함께 추가한다.
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Security — 인증/인가, 입력 검증, 비밀 값 관리
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -188,3 +188,7 @@ Content-Type: application/json
|
||||||
```
|
```
|
||||||
|
|
||||||
실제 구현에서는 클라이언트가 넘긴 `providerUserId`를 그대로 신뢰하기보다, 가능하면 서버에서 소셜 로그인 토큰을 검증한 뒤 provider 사용자 ID를 확정하는 방식을 권장한다.
|
실제 구현에서는 클라이언트가 넘긴 `providerUserId`를 그대로 신뢰하기보다, 가능하면 서버에서 소셜 로그인 토큰을 검증한 뒤 provider 사용자 ID를 확정하는 방식을 권장한다.
|
||||||
|
|
||||||
|
## 관련 문서
|
||||||
|
|
||||||
|
- [analysis/2026-06-16-project-analysis.md](./analysis/2026-06-16-project-analysis.md) — D4 데이터 모델 분석. 현재 코드는 `provider='email'` 만 구현(소셜 확장은 `추정`)이라는 현황 확인 참조.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Work Log — 세션 간 handoff, 시점성 작업 메모
|
||||||
|
|
||||||
|
이 카테고리에 문서를 추가할 때는 `../development/document-category-classification.md` 의 분류 기준을 따른다.
|
||||||
|
|
||||||
|
## 목록
|
||||||
|
|
||||||
|
_(아직 문서 없음)_
|
||||||
Loading…
Reference in New Issue