# task-2852 보고서 — ingest.js isAllowedIngestUrl egress 가드 미러 (crm.js 대칭)

- 팀: dev1-team (헤르메스 팀장 / 불칸 백엔드)
- 프로젝트: InsuRo (extension)
- 브랜치: `task/task-2851-dev1` (기존 플러밍 브랜치 — 새 PR 없이 이어서 커밋)
- 워크트리: `/home/jay/projects/.worktrees/InsuRo-task-2851-dev1`
- merge_policy: **none** (ANU 원격 검증·머지, 순서 B)
- 레벨: Lv.2

## Situation
`extension/background/crm.js`의 `isAllowedCrmUrl`은 exact-origin egress 가드가 완비되어 있음: prod host 포트(`""`/`443`)만 허용 + `url.search`/`url.hash`가 비어야 통과 + userinfo 거부.

## Complication
`extension/background/ingest.js`의 `isAllowedIngestUrl`은 **port/query/fragment를 미검증**하는 비대칭 갭 존재 (Codex 검토 지적). 결과적으로 `https://api.insuro.biz:444/...`, `...?x=1`, `...#frag`가 모두 통과. ingest는 **PII+분석+consent를 나르는 실 egress**라 crm보다 민감 → 동일하게 닫아야 함.

## Question
`isAllowedIngestUrl`을 crm.js와 동일한 규율로, 다른 로직은 건드리지 않고 surgical하게 닫을 수 있는가?

## Answer (수행 내역)

### 수정 파일 (2개, +15/-1)
1. `extension/background/ingest.js` — `isAllowedIngestUrl` 함수만 (+5/-1)
2. `extension/__tests__/prd1-ingest-core.test.ts` — 미러 테스트 (+11)

### 코드 변경 (`isAllowedIngestUrl`)
```diff
-  const isProdHost = url.protocol === "https:" && url.hostname === "api.insuro.biz";
+  const isProdHost = url.protocol === "https:" && url.hostname === "api.insuro.biz"
+                     && (url.port === "" || url.port === "443");
   const isLocalHost = url.protocol === "http:" && url.hostname === "localhost" && url.port === "8000";
   if (!isProdHost && !isLocalHost) return false;

   if (url.pathname !== INGEST_PATH) return false;
+
+  if (url.search !== "" || url.hash !== "") return false;

   return true;
```
- **변경 1**: prod host에 포트 검증 `(url.port === "" || url.port === "443")` 추가 → 비-443(예 :444) 거부.
- **변경 2**: pathname exact-match **뒤에** `url.search`/`url.hash` 비어있음 검증 추가 → 쿼리/프래그먼트 거부.
- userinfo(username/password) 거부는 이미 존재(135줄) → 손대지 않음.
- host 상수·조립·flag·localhost:8000 테스트 경로 전부 불변. `isAllowedIngestUrl` 외 로직 변경 0.

### 추가 테스트 (5개)
허용(→true) 2개:
- prod https api.insuro.biz 명시 `:443` 포트 + 정확한 path → true
- prod https api.insuro.biz 포트 생략(기본 443) + 정확한 path → true

거부(→false) 3개:
- prod 포트 불일치(비-443, `:444`) → false
- 쿼리스트링(`?x=1`) → false
- 프래그먼트(`#frag`) → false

(`insuro.biz` 프론트 apex 거부는 기존 테이블에 이미 존재 — 중복 미추가.)

## 테스트 결과
- `prd1-ingest-core.test.ts` 단독: **54 tests passed** (기존 49 + 신규 5), 회귀 0.
- 확장 전체 회귀: **Test Files 54 passed / Tests 814 passed**, 실패 0. single-egress·canary·contract-golden 골든 전부 유지.

## L1 스모크테스트 결과
- 서버 재시작: **해당없음** (확장 순수함수 egress 가드 — 백엔드 서버 무관)
- API 응답 확인: **해당없음**
- 실동작 검증(팀장 직접 독립 실행): 실제 `isAllowedIngestUrl`을 canonical vitest 로더로 관통 실행하여 task 수용기준 5개 케이스를 이름으로 확인:
  - ✓ 명시 :443 → true
  - ✓ 포트 생략(기본 443) → true
  - ✓ 비-443 :444 → false
  - ✓ 쿼리스트링 ?x=1 → false
  - ✓ 프래그먼트 #frag → false
  - ✓ (기존) insuro.biz 프론트 host → false 유지
  - 실행 커맨드: `npx vitest run extension/__tests__/prd1-ingest-core.test.ts -t "allowlist" --reporter=verbose` (17 passed)
- 스크린샷: 해당없음 (CLI 테스트 실행 로그로 대체 — 프론트엔드 UI 변경 없음)

> 참고: `require()` 직접 로드는 파일이 import/export를 배제한 플레인 스크립트(module.exports 조건부 노출, package.json type=module)라 빈 네임스페이스를 반환 → vitest가 canonical 로더이므로 vitest 관통 실행이 실동작 검증의 정본.

## 발견 이슈 및 해결
- `ingest.js:182` 진단 경고(`'consent' declared but never read`): 우리 커밋 diff에 **미포함**(git show HEAD로 확인). `assembleRequestBody` 영역의 기존 warning으로 이번 surgical 범위 밖 → 지시대로 손대지 않음. 신규 도입 아님.

## 커밋 / Push
- 커밋: `64090c5` `[task-2852] 불칸: ingest.js isAllowedIngestUrl — crm.js와 동일 egress 가드(prod :443만·query/fragment 거부) + 미러 테스트`
- Push: `6b0847e..64090c5  task/task-2851-dev1 -> task/task-2851-dev1` (성공)
- `git status --short`: `?? worktree-base.json`만 untracked (커밋 미포함, 정상)

## 머지 판단
- **머지 필요**: Yes (단, merge_policy=none → 팀장 머지 금지)
- **머지 주체**: ANU 원격 독립 검증·머지 (순서 B)
- **브랜치**: `task/task-2851-dev1` (기존 플러밍 PR에 편승 — 새 PR 생성 안 함)
- **머지 의견**: 2줄 surgical 변경, crm.js 검증된 패턴을 정확히 대칭 복제. 전체 814 테스트 회귀 0. 충돌 위험 낮음(단일 함수·전용 브랜치). affected_files는 ingest.js + __tests__로 crm.js·content.js·manifest 미접촉.

## 모델 사용 기록
- 불칸(백엔드): sonnet — egress 가드 로직 수정 + 테스트. (haiku 미사용: 보안 egress 가드라 정확성 우선)
- 팀장(헤르메스, Opus): 설계/분배/독립 L1 검증/통합만. 직접 코딩 없음.

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

