# 보안 핫픽스 prod 이식 — stg 후속 수정 얹기 · 사이트 카드 안내 prod 브랜치 (2026-10-19)

이 문서의 branch · head · worktree 필드는 서버 것이다. 사이트는 2절에 따로 적는다.

- branch: fix/security-hotfix-m4-m10-prod
- head: 52e65dd575a36fbcd6dfae4bede11bfc8c0f63a0
- worktree: /home/madsc/work/insuniverse-temp/.worktrees/security-hotfix/insuniverse-server/.worktrees/secfix-prod

push · PR · 머지는 하지 않았다(오케스트레이터 몫).

## 1. 서버

- 기준: origin/prod f1ee26e0 (PR #121 머지).
- 얹은 것: stg 후속 브랜치 fix/security-hotfix-followup-20261019 의 커밋 5586be60..2d0a2744 를 순서대로 `cherry-pick -x`.

| stg 원본 | prod 신규 | 제목 |
|---|---|---|
| 1acc2c25 | 1cc23d2931b4a3e4303e2cd6696e2ecb84220708 | fix(swagger): 배포 환경에서 swagger 화면을 명시 스위치 뒤로 닫는다 |
| 18bcfb14 | d72d23acdbce0e632eec198989e07acdf5d7acf7 | fix(board): 회원 게시판 요청을 토큰의 회원 기준으로 좁힌다 |
| e6b99ada | 93110f1aaee0aeec037ffe2068f9bffdf79a23ea | fix(authz): 분석 상세 가드가 asbId·acbId 를 소유 주문으로 대조한다 |
| 2d0a2744 | 52e65dd575a36fbcd6dfae4bede11bfc8c0f63a0 | fix(pg): 이미 폐기된 빌링키 삭제 응답을 이미 없음으로 읽는다 |

- 충돌: 4개 모두 무충돌. swagger 는 main.ts 의 `process.env.NODE_ENV !== 'production' && setupSwagger(app);` 자리에 `if (isSwaggerEnabled()) { setupSwagger(app); }` 로 자동 병합됐다. 즉 prod 에서는 SWAGGER_ENABLED==='true' 일 때만 swagger 를 마운트한다.
- 커밋 메시지: 17개 전부 100자 이하, Co-Authored-By 없음.

게이트 (prod 기준선 = origin/prod 의 `git archive` 사본과 비교):

1. 목록: origin/prod..HEAD = 17커밋 (기존 13 + 신규 4).
2. merge-tree: origin/prod 와 HEAD 무충돌 (결과 tree 30455fc7).
3. tsc (apps/api 루트 tsconfig.json): 기준선 19건 · 변경 19건. 서명 차집합 신규 0, 소멸 0.
4. jest (건드린 spec 7개): 52/52 통과.
   - apps/api/src/swagger.spec.ts (신규)
   - apps/api/src/modules/board/board/board-member-access.spec.ts (신규)
   - apps/api/src/modules/auth/user/own-analysis.guard.spec.ts
   - apps/api/src/modules/medical/medical-access.spec.ts
   - libs/pg/src/toss/toss-billing-delete.spec.ts
   - apps/api/src/modules/card/card-billing-revoke.spec.ts (prod 카드 커밋과 맞물림)
   - apps/api/src/modules/card/card.repository.spec.ts
5. nest build: 통과 (rc 0). 산출은 dist/apps/api/apps/api/src/main.js.
6. DI 부팅: AppModule 을 `NestFactory.createApplicationContext` 로 올렸다 (listen · 스케줄러 없음. DB 환경 변수는 비웠고 REDIS_URL 은 닫힌 루프백 포트). 결과 DI_BOOT_OK.
   - 부팅에는 FCM_PRIVATE_KEY 자리에 일회용 RSA 키를 셸 변수로만 넣었다. 파일에는 남기지 않았다.
   - 음성: FCM_PRIVATE_KEY 를 비우면 DI_BOOT_FAIL, rc 1. 게이트가 실제로 가른다.
7. ESLint (레거시 모드, `ESLINT_USE_FLAT_CONFIG=false`, 변경 TS 16개): 변경 후 0건. 기준선에는 apps/api/src/main.ts:42 의 no-unused-expressions 1건이 있었고, swagger 커밋이 이를 없앴다.
8. prettier: auth 쪽 파일은 `--write` 하지 않았다 (전체 재포맷 없음).
9. 격리 DB 통합 spec (itspec): 이번 변경 파일에 없어 돌리지 않았다.

원자료: 서버 git dir 의 `gates/` 아래 (tsc-head/base.txt · sig-head/base.txt · jest-head.json · nest-build.log · di-boot.log · di-boot-neg.log · eslint-head/base.txt).
경로: /home/madsc/work/insuniverse-temp/.git/worktrees/security-hotfix/modules/insuniverse-server/worktrees/secfix-prod/gates/

## 2. 사이트 — 카드 삭제 실패 안내의 prod 브랜치

- worktree: /home/madsc/work/insuniverse-temp/insuniverse-site/.worktrees/card-delete-notice-prod
- branch: fix/card-delete-failure-notice-20261019-prod (upstream 은 설정하지 않았다. `worktree add` 가 origin/prod 를 추적하게 만든 것을 `git branch --unset-upstream` 으로 풀었다. 그대로 두면 평범한 push 가 prod 로 나갈 수 있다.)
- head: bde15c3d881c7bebe645bd303b6781b57754a5b6
- base: origin/prod 93904403e7a28a1d9c57d09f313266e236b79de5 (PR #97 머지)
- 얹은 것: stg 수정 ed40fcc7 를 `cherry-pick -x` → bde15c3d. 대상 파일은 front/src/api/cardApi.ts · front/src/pages/mypage/editPaymentCard/EditPaymentCardPage.tsx · EditPaymentCardPage.test.tsx (신규). 무충돌.
- lock: front/package-lock.json 블롭이 stg 와 prod 에서 같다 (e81d46b9). 그래서 메인 front/node_modules 를 심볼릭 링크했고 npm ci 는 하지 않았다.

게이트 (prod 기준선 = origin/prod 의 `git archive` 사본):

1. tsc (front/tsconfig.node.json): 기준선 158건 · 변경 158건, 신규 0. `npm run build` 안의 tsc 는 files: [] 라 0파일을 본다. 실질 판정은 이 설정으로만 한다.
2. ESLint (변경 3파일): 0 error. 경고 1건(EditPaymentCardPage.tsx 의 useEffect exhaustive-deps, initCardData)은 기준선에도 같은 경고가 있다 (행 200 → 203).
3. vitest (src/pages/mypage/editPaymentCard · src/api): 4 suite · 8 test 전부 통과.
4. `npm run build`: 통과 (vite 1188 모듈 변환).
5. 생성물: front/tsconfig.node.tsbuildinfo (tsc composite 부산물)는 지웠다. 워크트리 status 는 clean.

원자료: 사이트 git dir 의 `gates/` 아래 (tsc-head/base.txt · sig-head/base.txt · eslint-head/base.txt · vitest.json · vite-build.log).
경로: /home/madsc/work/insuniverse-temp/.git/modules/insuniverse-site/worktrees/card-delete-notice-prod/gates/

## 3. 남은 것 (사람 몫)

- 서버 PR 을 만들면 base 가 prod 이므로 closing keyword 는 Development 에 연결되지 않는다. addCloseIssueReferences 로 연결하고 `gh pr view --json closingIssuesReferences` 로 확인한다.
- 토스 ALREADY_REMOVED_BILLING_KEY (2d0a2744) 는 샌드박스 실측 값이다. 운영 키의 실제 응답은 확인하지 않았다. 커밋 본문에도 같은 한계가 적혀 있다.
- swagger 는 prod 에서 SWAGGER_ENABLED 가 `true` 가 아니면 닫힌다. 배포 환경 변수에 이 값이 켜져 있는지 확인한다.
- 사이트 카드 삭제 실패 안내는 서버 prod 의 M10 빌링키 폐기 실패 경로와 짝이다. 서버 verify 노트 기준으로, 이 실패 경로가 열려야 사용자 체감이 생긴다. 사이트 단독 배포의 효과는 확인하지 않았다.
