# task-2837 — PR-D2 isAllowedCrmUrl prod 호스트 포트 검증 (egress allowlist exact-origin)

- 팀: dev1-team (헤르메스)
- 브랜치: `task/task-2836-dev1` (PR #120, OPEN) — 이어서 커밋(새 PR 없음)
- 워크트리: `/home/jay/projects/InsuRo/.worktrees/task-2836-dev1`
- merge_policy: none (머지 금지 — PR open 유지)
- 커밋: `1c2a9d6`

## SCQA

### Situation
InsuRo 확장(Phase2 PR-D2)의 CRM 단일 egress choke point는 `isAllowedCrmUrl()`(`extension/background/crm.js`)의 fail-closed allowlist 하나로 통제된다. localhost 테스트 경로는 `:8000` 포트까지 엄격히 검증하지만 prod(`insuro.biz`) 경로는 포트를 검증하지 않았다.

### Complication
`isProdHost` 판정이 `protocol==="https:" && hostname==="insuro.biz"` 만 확인 → `https://insuro.biz:444/...` 같은 **비-443 포트**가 allowlist를 통과. SSRF는 아니나, `insuro.biz`에 443 외 리스너가 존재할 경우 CRM PII가 예기치 않은 목적지로 유출될 수 있는 **egress-scope 갭**. single-egress의 exact-origin 원칙 위반.

### Question
prod 목적지 검증을 localhost와 대칭이 되도록, **표준 https 포트(443 또는 포트 생략)만** 허용하도록 좁힐 수 있는가? (다른 로직 무변경, 회귀 0)

### Answer
`isProdHost` 한 줄에 포트 검증을 추가하는 surgical 수정으로 갭을 종결. localhost 로직·다른 함수·주석은 무변경.

## 변경 내용 (git diff --stat: 2 files, +11 -1)

`extension/background/crm.js` (line 69):
```diff
-  const isProdHost = url.protocol === "https:" && url.hostname === "insuro.biz";
+  const isProdHost = url.protocol === "https:" && url.hostname === "insuro.biz"
+                     && (url.port === "" || url.port === "443");
```

`extension/__tests__/prd2-crm-core.test.ts` (+9): 거부 테이블에 `insuro.biz:444` 거부 1건, 허용 케이스 2건(`:443` 명시 / 포트 생략) 추가.

## 검증 (L1 스모크테스트)
- 서버 재시작: **해당없음** — 순수 `new URL()` allowlist 함수(브라우저 확장 로직). 구동 서버/API/브라우저 없음.
- API 응답 확인: 해당없음
- 스크린샷: 해당없음
- **실행 검증(팀장 직접 재실행)**: `npx vitest run extension/__tests__/`
  - 결과: **Test Files 15 passed (15) · Tests 372 passed (372)** — 회귀 0
  - `prd2-crm-core.test.ts`: 51→**54 passed** (신규 3건 모두 실행·통과 확인)
  - 신규 3건 개별 확인:
    - `거부: prod 포트 불일치(비-443)` (`https://insuro.biz:444`) → false ✅
    - `허용: 명시 :443 포트` (`https://insuro.biz:443`) → true ✅
    - `허용: 기본 포트(포트 생략)` (`https://insuro.biz`) → true ✅
- git diff 실측: 정확히 `crm.js` + `prd2-crm-core.test.ts` 2파일만 변경(범위 준수).

## 발견 이슈 및 해결
- `prd2-crm-core.test.ts:24` `createOpaqueValue` 미사용 import 경고(TS6133) — **범위 외/기존 존재**(HEAD~1에 이미 있음, PR-D2 원본). task 지시("isAllowedCrmUrl 외 건드리지 마라") 및 surgical 원칙에 따라 미수정.

## 머지 판단
- **머지 필요**: No (merge_policy=none — 머지 금지, PR #120 open 유지)
- **브랜치**: task/task-2836-dev1 (원격 head = `1c2a9d6`, push 완료)
- **머지 의견**: 확장 전체 372 회귀 0, 스코프 clean(2파일). PR #120에 반영됨. 최종 머지는 ANU 독립검증 후 상위 판단.

## 모델 사용 기록
- 불칸(백엔드, sonnet): crm.js surgical 수정 + 테스트 3건 (구현·1차 검증)
- 헤르메스(팀장, Opus): 설계/스코프 확정·diff 실측·vitest 독립 재실행·push
- haiku 미사용

## 팀원 구성 비고
- 이리스/아테나/아르고스 미소환 — 1줄 백엔드 egress-guard 수정이라 불칸 단독 위임이 적정(디자인/프론트/별도 테스터 불요).

## 세션 통계
- 총 도구 호출: 0회

