--- 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). 외부 자료 미사용 → 모든 사실은 코드 직접 확인(`확인됨`). 추정 항목은 `추정`/`미확인` 으로 명시했으며 사실로 격상하지 않았다. > > 출처: work-session `20260616-111711` 의 `research/analysis.md` 를 정제한 정본 기록. ## 요약 Java/Maven 웹앱(Spring Boot 3 MVC + MyBatis + PostgreSQL, JSP 뷰) 전면 read-only 분석. 4개 차원으로 정리한다: 아키텍처(D1), 보안(D2, 최우선), 코드품질/기술부채(D3), 도메인/기능(D4). 상위 결론: **보안 기초는 의외로 탄탄**(SQLi 표면 0, PBKDF2 견고, CSRF 더블서밋, zip-slip/path-traversal/zip-bomb 방어, XSS escape 일관)하나, **프로토타입 잔재성 dead code 와 미완성 기능이 화석으로 남아** 신규 개발자에게 혼란·인증 함정을 유발한다. 서비스 레이어 부재로 fat controller 가 발생했고, 테스트가 거의 전무하다. ## 주요 발견 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 태그(``, `${_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 `` 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 레이어 존재" 를 전제했다면 충돌(아래 종합 판단 참조). - **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**(확인됨): `` 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` 가 `` + `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 불가. (참조: [db-update-query-generator.md](../db-update-query-generator.md)) - **커버리지 갭**: 컨트롤러·매퍼·인증·업로드·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 만 구현(**추정**). 상세: [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` 필터(확인됨, 일관). ### 사용자 플로우 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). → 후속 작업에서 전제 재확인 필요. ## 미해결 / open_questions 1. 게임 좋아요·댓글의 서버 영속화는 의도된 미완성인가, 폐기된 기능인가? (스키마·매퍼는 완비, 엔드포인트만 없음) — 코드만으로 의도 판별 불가(`미확인`). 2. 세션 쿠키 Secure/SameSite·HTTPS 강제는 배포 톰캣/리버스프록시 설정에 의존 — 저장소 코드 밖이라 `미확인`. 프로덕션 설정 확인 필요. 3. `provider`/`provider_user_id` 컬럼 = 소셜로그인 확장 예정 스키마인지(현재 email 전용) — `추정`. 4. spring-boot 3.5.14-SNAPSHOT 을 의도적으로 SNAPSHOT 유지하는 이유(특정 미릴리스 픽스 의존?) — `미확인`. 로컬 빌드 영향(온라인 의존·오프라인 빌드 불가)은 [usage/local-setup.md](../usage/local-setup.md) §1·§7 참조. 5. 의존성 CVE 스캔 미수행(외부 조회 안 함) — 별도 `mvn dependency-check` 또는 OSV 조회 권장. ## 관련 링크 (References) - 출처(work-session): `.atp/work-session/20260616-111711/research/analysis.md` - [db-update-query-generator.md](../db-update-query-generator.md) — `DbUpdateQueryGeneratorTest` 운영 도구(D3 테스트 항목 관련) - [user-signup-schema.md](../user-signup-schema.md) — `users`/`user_auth_identities` 스키마 상세(D4 데이터 모델 관련) - 카테고리 분류 기준: [development/document-category-classification.md](../development/document-category-classification.md)