# task-2838 보고서 — PR-D2 isAllowedCrmUrl egress 게이트 마감

## 메타
- **작업 ID**: task-2838
- **팀**: dev1-team (헤르메스 팀장)
- **레벨/정책**: Lv.2 · merge_policy=none (머지 금지) · 브랜치 `task/task-2836-dev1`(PR #120)에 이어서 커밋(새 PR 금지)
- **프로젝트**: InsuRo (extension/**)
- **워크트리**: `/home/jay/projects/InsuRo/.worktrees/task-2836-dev1`
- **작업 시각**: 2026-07-23 01:12~01:18 (KST server time)

## Situation (상황)
직전 커밋 `1c2a9d6`(task-2837)로 prod 호스트 포트 갭(`:444`)은 표준 https 포트(443/생략)만 허용하도록 수정 완료. Codex 재검토가 남긴 2개 지적을 마감하는 surgical follow-up.

## Complication (문제)
1. **`:444 → fetch 미호출` 통합 케이스 미증명**: allowlist bool=false 는 단위 테스트되나, egress 전송 함수(searchCandidates/createCustomer→crmFetch)를 실제 관통시켜 fetch가 호출되지 않고 `URL_NOT_ALLOWED`를 반환하는 end-to-end 케이스가 부재.
2. **쿼리스트링/프래그먼트 미검증**: `isAllowedCrmUrl`이 pathname만 exact-match하고 `url.search`/`url.hash`를 안 봐서 `https://insuro.biz/<허용경로>?foo=bar`가 통과. (URL은 코드 상수 구성이라 실주입 벡터는 아니나 exact-origin·fail-closed 원칙상 닫음.)

## Question (질문)
`isAllowedCrmUrl` 외 로직을 건드리지 않고, 쿼리/프래그먼트를 fail-closed로 거부하며, 전송 함수 관통 통합 테스트로 :444 no-fetch를 실증할 수 있는가?

## Answer (해결)
### 코드 수정 (surgical — 정확히 1줄)
`extension/background/crm.js` `isAllowedCrmUrl`의 pathname exact-match 라인(74행) 바로 뒤에 1줄 추가 (75행):
```js
if (url.search !== "" || url.hash !== "") return false;
```
→ 허용 경로라도 쿼리스트링/프래그먼트가 있으면 거부. 기존 검증(username/password·protocol·hostname·port·pathname)은 전혀 변경하지 않음.

### 테스트 추가 (신규 2건 = describe 2개, 총 6 it)
신규 파일 `extension/__tests__/prd2-crm-egress-gate.test.ts` (107줄):
1. **:444 no-fetch 통합** — `searchCandidates`/`createCustomer`를 `https://insuro.biz:444/...`로 유도 → `{ ok:false, code:"URL_NOT_ALLOWED" }` + `fetchSpy` 0회 호출. 단순 allowlist bool이 아니라 crmFetch 경유 전송 함수를 실제 관통.
2. **쿼리/프래그먼트 거부** — `.../<path>?x=1`, `.../<path>?foo=bar`, `.../<path>#frag` → false. 순수 경로 `.../<path>` → 여전히 true (회귀 확인).

## 수정/생성 파일 목록
- `extension/background/crm.js` — +1줄 (isAllowedCrmUrl 쿼리/프래그먼트 거부)
- `extension/__tests__/prd2-crm-egress-gate.test.ts` — 신규 +107줄

`git diff --stat 1c2a9d6 HEAD`:
```
extension/__tests__/prd2-crm-egress-gate.test.ts | 107 +++++++++++++++++++++++
extension/background/crm.js                      |   1 +
2 files changed, 108 insertions(+)
```
→ crm.js + 테스트 외 변경 0. src/**, server/**, .github/**, docs/** 침범 0.

## 커밋 (PR #120 브랜치 이어서, 새 PR 금지)
- `af8fb37` [task-2838] 불칸: isAllowedCrmUrl 쿼리/프래그먼트 거부 — exact-origin fail-closed 마감
- `e862260` [task-2838] 아르고스: :444 no-fetch 통합 + 쿼리/프래그먼트 거부 테스트 2건
- push: `1c2a9d6..e862260 task/task-2836-dev1` → **PR #120(OPEN) 마지막 커밋 e862260 반영 확인** (새 PR 미생성)

## 테스트 결과 (전체 회귀)
`npx vitest run extension/__tests__/` (팀장 독립 재실행):
```
Test Files  16 passed (16)
     Tests  378 passed (378)
  Duration  2.99s
```
→ 신규 `prd2-crm-egress-gate.test.ts` (6 tests) 포함. **실패 0건, 회귀 0**. (task 기준 372 이상 — 현재 378)

## L1 스모크테스트 결과
- **서버 재시작**: 해당없음 — Chrome MV3 확장 background 순수 로직 모듈(서버 프로세스 아님)
- **API 응답 확인**: 해당없음(실동작 목표가 "egress를 실제로 안 나가게 함") — 대신 **통합 테스트로 실증**: searchCandidates/createCustomer가 crmFetch를 경유해 `:444` URL에서 fetch 0회 호출 + `URL_NOT_ALLOWED` 반환을 end-to-end로 관통 확인 (vitest 실행 PASS)
- **스크린샷**: 해당없음 — UI 없음(background egress 모듈)
- **판정**: L1 통과 — 이 코드의 "실동작"은 egress 함수 관통 통합 테스트로 커버되며 실제 코드 경로를 관통해 PASS. pytest-only가 아닌 실제 전송 함수 관통 검증.

## 머지 판단
- **머지 필요**: No (merge_policy=none — 머지 금지)
- **브랜치**: task/task-2836-dev1 (PR #120, OPEN)
- **워크트리 경로**: /home/jay/projects/InsuRo/.worktrees/task-2836-dev1
- **머지 의견**: 이 task는 PR #120 egress 게이트 마감용 follow-up. 머지는 ANU 재검증 후 아누/회장 판단. Codex 잔여 2건(쿼리/프래그먼트 + :444 통합)이 코드+테스트로 마감됨. 회귀 0, 스코프 clean(crm.js 1줄 + 테스트만).

## 발견 이슈 및 해결
- 이슈 없음. surgical 스코프 그대로 완료. (remote 이전 안내 메시지 출력됐으나 push는 기존 origin으로 정상 완료 — 저장소 리다이렉트 경고일 뿐 기능 영향 없음.)

## 모델 사용 기록
- 불칸(백엔드): sonnet — crm.js 1줄 수정 (일반 로직 수정)
- 아르고스(테스터): sonnet — 테스트 2건 작성 + 회귀 검증 (테스트 코드 작성)
- haiku 미사용. 팀장(헤르메스, Opus)은 설계/분배/독립 재검증만 수행(직접 코딩 없음).

## 비고
- **ANU 독립검증 대상**: 머지 판정은 ANU normal collector(독립 ANU key)가 회수 후 수행. executor self-key 자가검증 금지.
- 다음 흐름(참고): task-2838 완료 → ANU 재검증(회귀 0 확인) → PR #120 머지 판단 → PR-E(웹앱 V-path 재배선).

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

