---
task_id: task-3000
role: aine-uxui
scope: read-only investigation (no code changes made)
date: 2026-08-22
---

# task-3000 — push endpoint 호스트 화이트리스트 증거 조사 (아네)

## 1. 서버측 실측 — `/home/jay/projects/InsuRo/supabase/functions/send-push/index.ts`

- `subscription.endpoint` 를 두 곳에서 사용한다:
  - line 24: `const audience = new URL(subscription.endpoint).origin;` — VAPID JWT 의 `aud` 클레임으로 씀. `new URL()` 이 파싱 가능한 절대 URL이기만 하면 되고, 스킴/호스트에 대한 별도 검증·가정은 코드에 없음.
  - line 67: `const response = await fetch(subscription.endpoint, { method: 'POST', ... })` — endpoint 를 **그대로 fetch 대상 URL로 사용**. 여기가 SSRF/오리진 문제의 실제 착지점.
  - 스킴 검증(https 강제) 없음, 호스트 화이트리스트 없음. **현재 코드는 endpoint 가 무엇이든 그대로 믿고 요청을 쏜다.**
- 실패 처리: `res.status === 410 || res.status === 404` 이면 `is_active=false` 로 비활성화(line 150-153). 다른 상태코드(예: 화이트리스트로 요청 자체를 막아서 발생하는 4xx/네트워크 에러)는 `catch (e) { console.error(...) }` 로만 로그되고 구독은 비활성화되지 않음 — 즉 **화이트리스트가 정상 호스트를 잘못 막으면, 그 구독은 "죽었지만 is_active=true로 영원히 남아" 조용히 무응답 상태가 된다** (해당 사용자는 재구독하지 않는 한 복구 경로가 없음).
- `send-push` 외에 push 관련 함수는 `/home/jay/projects/InsuRo/supabase/functions/get-vapid-key/index.ts` 뿐 — 이 함수는 `VAPID_PUBLIC_KEY` env 를 그대로 반환할 뿐 endpoint 를 다루지 않음. 다른 push 관련 edge function 없음(확인됨, `supabase/functions` 전수 grep).

## 2. 프론트 실측 — `/home/jay/projects/InsuRo/src/lib/push-utils.ts` (읽기만 함, 수정 없음)

- `registerPushSubscription()` (line 191-246): `navigator.serviceWorker.register('/sw-push.js')` → `registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidKey) })`. **VAPID `applicationServerKey` 를 항상 사용**한다 — 이는 W3C 표준 경로이며, 이 키를 요구하지 않는 비표준 벤더(예: 구형 GCM sender_id 방식)는 이 코드로는 애초에 지원되지 않는다.
- `subJson = subscription.toJSON(); endpoint = subJson.endpoint!` — 브라우저가 반환한 endpoint 를 그대로 RPC(`chat_register_push_subscription`)에 전달. 프론트에서도 endpoint 형식 검증 없음.
- customer 경로는 RPC만 사용(`chat_register_push_subscription`/`chat_unsubscribe_push`), agent 경로는 테이블 직접 접근(`push_subscriptions` insert/update) — 둘 다 endpoint 를 그대로 저장.
- `/home/jay/projects/InsuRo/public/sw-push.js` (서비스워커) — `push` / `notificationclick` 이벤트 핸들러만 있고 endpoint 관련 로직 없음(표준 패턴).

## 3. DB 내 기존 endpoint 표본

- `push_subscriptions` 테이블 **현재 실측 행 수 = 0** — `/home/jay/projects/InsuRo/supabase/migrations/20260822T000003_task2996_push_subscriptions_caller_binding.sql` 81행 주석: "현재 테이블 행 수 = 0 (2026-08-22 실측)". → **프로덕션 실측 endpoint 표본으로 화이트리스트를 검증할 방법이 없다.** 화이트리스트는 순전히 외부 벤더 사양/관측치에 의존해야 함.
- 리포 전체에서 endpoint 예시 문자열은 딱 하나뿐: `/home/jay/projects/InsuRo/src/lib/__tests__/push-utils.test.ts:178`
  `endpoint: "https://fcm.googleapis.com/fcm/send/SECRET-ENDPOINT-9f3a7b"`
  — 이건 **테스트 픽스처(조작된 값)** 이지 실제 캡처된 프로덕션 값이 아님. 다른 브라우저(Firefox/Safari/Edge)의 샘플은 리포 어디에도 없음(확인됨).

## 4. 웹푸시 벤더 호스트 조사 (2026-08 기준, WebSearch/WebFetch)

가장 신뢰도 높은 단일 출처: **Pushpad 사(상용 웹푸시 벤더, ~2억 건 구독 데이터 기반)가 관리하는 화이트리스트** — https://github.com/pushpad/known-push-services (README: "The list of push service hosts is complete and covers all major browsers (Chrome, Firefox, Opera, Edge, Safari, etc.)"). 마지막 커밋 2026-06-01(오늘로부터 약 2.5개월 전 — 비교적 최신).

`whitelist` 파일 raw 내용 (확인됨, `curl` 로 직접 fetch):
```
android.googleapis.com
fcm.googleapis.com
jmt17.google.com
updates.push.services.mozilla.com
updates-autopush.stage.mozaws.net
updates-autopush.dev.mozaws.net
*.notify.windows.com
*.push.apple.com
```

호스트별 상세:

- **`fcm.googleapis.com`** / 브라우저: Chrome, Chromium, Brave, Opera(데스크톱), Samsung Internet, Edge(Android) / 근거: developer.chrome.com 블로그("Web Push Interoperability Wins"), Microsoft Q&A("Chromium Edge for Android gets Google endpoint instead of windows.com endpoint"), Samsung Developer Forum, Brave Community 포럼 / 확신도: **확인됨** (다수 독립 출처 일치). 경로 형태는 레거시 `/fcm/send/<token>` 이 여전히 관측되나(pushpad, 테스트 픽스처와 일치), FCM 은 Web Push Protocol 을 그대로 얹는 방식이라 경로 자체를 검증 조건으로 쓰면 안 됨(host만 검증해야 함) — Chrome 공식 블로그가 경고.
- **`android.googleapis.com`** (레거시 GCM, `/gcm/send/...`) / 브라우저: 구형 Chrome(v52 이전)·구형 Opera Android·구형 Samsung Internet 잔존 세션 / 근거: pushpad whitelist 자체 포함 + Firebase 마이그레이션 문서("Google decommissioned FCM's legacy HTTP endpoint... June 20, 2024" — 단, 이건 fcm.googleapis.com/fcm/send 레거시 서버측 발신 방식 종료 얘기고, `android.googleapis.com/gcm/send` 클라이언트 endpoint 자체가 살아있는 기존 구독에 여전히 쓰일 수 있는지는 **미확인**) / 확신도: **추정** — pushpad가 리스트에 유지하고 있다는 사실 자체가 "아직 관측된다"는 신호지만, 우리 쪽에서 직접 검증한 사실은 아님. ANU 판단 필요.
- **`jmt17.google.com`** / 브라우저: "Chrome staging endpoint" (pushpad 커밋 메시지, 2026-06-01 신규 추가) / 근거: pushpad 저장소 커밋 로그(확인됨, `gh`/`curl` 직접 조회) / 확신도: **확인됨(존재)**이나 실제 어떤 사용자 세그먼트에 뜨는지는 pushpad 커밋 메시지 이상의 상세 근거를 못 찾음 — 프로덕션 트래픽에 안전하게 포함해도 되는지는 **추정**.
- **`updates.push.services.mozilla.com`** / 브라우저: Firefox 데스크톱(전 버전) / 근거: Mozilla 공식 문서(mozilla-push-service.readthedocs.io), Mozilla Support(support.mozilla.org/en-US/kb/push-notifications-firefox) / 확신도: **확인됨**.
- **`updates-autopush.stage.mozaws.net` / `updates-autopush.dev.mozaws.net`** / Firefox Nightly/스테이징·개발 채널 / 근거: pushpad whitelist / 확신도: **확인됨(존재)** — 다만 이건 Mozilla 내부 스테이징 환경이라 **일반 프로덕션 사용자에게는 나타나지 않을 가능성이 높음**(포함 여부는 판단 필요, 포함해도 리스크는 낮음 — 공격 표면이 아니라 정상 트래픽이 절대 안 옴).
- **Firefox for Android** / 근거: web-push-libs GitHub 이슈 스레드("Firefox for Android uses a combination of the Mozilla Web Push service and Google's Cloud Messaging platform")·Bugzilla / 확신도: **불확실** — 문서에 따라 "데스크톱과 같은 `updates.push.services.mozilla.com`" 이라는 설명과 "GCM과 혼합"이라는 설명이 혼재. **엔드포인트 호스트가 fcm.googleapis.com 으로 나올 가능성을 배제 못함** — 위 fcm.googleapis.com 허용으로 커버될 여지는 있으나 확정 불가. ANU 판단 필요.
- **`*.push.apple.com`** (구체적으로 `web.push.apple.com`) / 브라우저: Safari (macOS 13+, iOS/iPadOS 16.4+ PWA) / 근거: WebKit 공식 블로그(webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados), Apple Developer 포럼, WWDC22 세션 10098 / 확신도: **확인됨**. 공식 권고: "allow URLs from any subdomain of push.apple.com" — 즉 **정확히 `web.push.apple.com` 만 허용하면 향후 Apple이 서브도메인을 바꿀 때(예: 리전별 분산) 깨질 수 있다.** 와일드카드(`*.push.apple.com`) 또는 최소 서브도메인 매칭이 안전.
- **`*.notify.windows.com`** (예: `wns2-sg2p.notify.windows.com`) / 브라우저: **Edge 데스크톱(Chromium 기반이지만 데스크톱은 WNS 유지)** / 근거: Microsoft Learn(WNS overview), Microsoft Q&A 스레드(2026, "Chromium Edge for Android gets Google endpoint instead of windows.com endpoint for push notifications" — 이 제목 자체가 **Edge 데스크톱은 지금도 windows.com endpoint 를 쓰고, Android 만 최근 Google endpoint로 바뀌었다**는 것을 시사) / 확신도: **확인됨(Edge 데스크톱=WNS)**. ★ **서브도메인이 가변적**이라고 Microsoft Learn 공식 문서가 명시("subdomain of the channel URI is subject to change... should not be considered when validating") — **`notify.windows.com` 을 정확 문자열로 검증하면 안 되고 접미사(suffix) 매칭이 필수.**
- **Brave** / 근거: Brave Community 포럼("Use Google services for push messaging" 설정 존재) / 확신도: **확인됨(기본은 fcm.googleapis.com)** — 단, Brave 는 "Google 서비스 사용" 토글을 끌 수 있는 옵션이 있고, 껐을 때 어떤 호스트를 쓰는지(자체 push 서비스 유무)는 **미확인**. 이 옵션을 끈 사용자가 있다면 화이트리스트 밖 호스트가 나올 수 있음.
- **Opera** / 근거: pushpad 블로그("Opera uses Google Firebase Cloud Messaging (FCM), which is also used by Chrome and some other Chromium-based browsers like Brave") / 확신도: **추정** (2차 출처, Opera 공식 문서로 직접 확인은 못함).
- **Vivaldi** / 확신도: **미확인** — 직접 검색하지 않음(Chromium 기반이라 fcm.googleapis.com 을 쓸 개연성은 높으나 공식/독립 출처를 확보하지 못함).
- **중국 브라우저(UC Browser, MIUI/Xiaomi 브라우저, QQ브라우저 등)** / 확신도: **미확인** — 검색 결과가 결정적이지 않았음(UC Browser는 Blink 기반이라 표준 Web Push를 쓸 가능성은 있으나, 실제 endpoint 호스트를 특정한 출처를 못 찾음). InsuRo가 한국 보험 서비스로 타깃 사용자층이 이 브라우저를 쓸 가능성은 낮다고 보이나 이는 추정이지 실측이 아님.

## 5. 판정

- **확실히 허용해야 할 호스트** (다수 독립 출처로 확인됨, 화이트리스트 방식 채택 시 최소 포함 필수):
  - `fcm.googleapis.com` (Chrome/Chromium/Brave/Opera/Samsung Internet/Edge-Android — 사실상 최다수 사용자 커버)
  - `updates.push.services.mozilla.com` (Firefox 데스크톱)
  - `*.push.apple.com` (와일드카드 필수 — Safari, `web.push.apple.com` 하드코딩은 위험)
  - `*.notify.windows.com` (와일드카드 필수 — Edge 데스크톱, 서브도메인 가변)
- **불확실해서 ANU 판단이 필요한 호스트**:
  - `android.googleapis.com` (레거시 GCM — 아직 남은 구형 세션 지원 여부)
  - `jmt17.google.com` (Chrome 스테이징 — 포함 여부 및 리스크)
  - `updates-autopush.stage.mozaws.net` / `updates-autopush.dev.mozaws.net` (Firefox 스테이징/개발 — 포함해도 무해하지만 불필요할 수 있음)
  - Firefox for Android 가 별도 호스트를 쓰는지 여부(문서 상충)
  - Brave "Google 서비스 끄기" 옵션 사용자의 대체 호스트
  - Vivaldi, 중국계 브라우저 — 전혀 미확인
- **화이트리스트 방식의 위험 평가**:
  - 서버 코드(`send-push/index.ts`)는 화이트리스트 거부 시 별도 처리 로직이 없다 — 위 "1." 항목대로, 화이트리스트에 없는 **정상 호스트**를 만나면 `sendWebPush` 가 fetch 에러(또는 요청 자체를 사전에 막았다면 그 앞단)로 실패하고, 현재 로직상 410/404 가 아니므로 **구독은 비활성화되지 않고 계속 "정상"으로 남아 매번 실패만 반복**한다 — 사용자는 알림이 영영 안 오는데 시스템은 이를 감지도 못한다.
  - 현재 DB 표본이 0건이라 화이트리스트를 프로덕션 실측으로 사전 검증할 수 없다 — **배포 후 첫 실사용 트래픽이 곧 검증**이 된다는 뜻이므로, 화이트리스트는 최대한 넓게(위 "확실" 4개 + 불확실 항목 중 무해한 것들 포함) 잡고, 화이트리스트 거부 시 최소한 서버 로그에 "어떤 호스트가 거부됐는지"를 남기는 관측 장치가 없으면 향후 원인 파악 자체가 불가능하다(이건 코드 변경 제안이라 범위 밖이지만 리스크로 기록).
  - 종합: **호스트 화이트리스트 도입 자체는 온당하나, (a) 완전한 사전 검증이 불가능한 상태에서 (b) 거부 시 무성 실패로 이어지는 현재 코드 구조**가 겹치면 "정상 사용자가 알림을 영영 못 받는다"는 최대 리스크가 실현될 확률이 낮지 않다. ANU 승인 전 최소한 (i) 와일드카드 서브도메인 매칭 여부, (ii) 화이트리스트 거부를 관측 가능하게 만들지 여부를 확정할 것을 권고.
