Skip to content

[fix/MAT-304-307-311] native 보안·딥링크·잡버그 묶음#311

Merged
sterdsterd merged 20 commits intodevelopfrom
fix/mat-304-307-311-misc
May 4, 2026
Merged

[fix/MAT-304-307-311] native 보안·딥링크·잡버그 묶음#311
sterdsterd merged 20 commits intodevelopfrom
fix/mat-304-307-311-misc

Conversation

@sterdsterd
Copy link
Copy Markdown
Collaborator

@sterdsterd sterdsterd commented May 3, 2026

Summary

학생 앱의 보안 가드(FCM 로그/WebView navigation policy/CDN SRI), 딥링크 콜드스타트 안정성, 로그인·렌더·시리얼라이저 잡버그를 묶어 정리한다. 9개 sub-issue 가 한 PR.

  • 보안: FCM 토큰/페이로드 로그 __DEV__ 가드, WebView originWhitelist 는 외부 앱 fallback 방지를 위한 통과 layer 로 두고 onShouldStartLoadWithRequest 에서 deny-by-default navigation policy 강제, KaTeX/kopubbatang CDN 3개에 SHA-384 SRI + crossorigin.
  • 딥링크: 콜드스타트 시 navigationRef busy-wait polling (max 3s) 을 module-level subscriber Set + NavigationContainer.onReady/onStateChange fan-out 기반 이벤트 대기 (max 30s) 로 전환. auth hydration 후 StudentApp route 등록까지 기다려 저사양 기기에서도 알림 손실 방지.
  • OAuth 정리: useNativeOAuth 도입 (PR-4 머지) 이후 사용되지 않는 useSocialLoginCallback 훅 + 라우터 호출 + 배럴 export 전부 삭제. 앱 내 화면 이동용 url scheme 핸들러는 보존.
  • 잡버그: LoginScreen 핸들러 unhandled rejection 차단(void), serializeJSONToHTML JSON.parse 페일세이프, PointingScreen render body 사이드이펙트(console.warn) 제거, useFcmToken 미사용 listener ref 정리.

Linear

  • MAT-304 — 보안 가드 강화 (qna 제외 3건)
    • MAT-454 — FCM 토큰/페이로드 console.log __DEV__ 가드
    • MAT-455 — ContentWebView navigation policy deny-by-default + 외부 navigation 차단
    • MAT-456 — KaTeX/kopubbatang CDN SRI hash 추가
  • MAT-307 — OAuth/딥링크 콜드스타트 정리
    • MAT-457 — useSocialLoginCallback OAuth URL scheme 핸들러 제거
    • MAT-458 — useDeepLinkHandler 이벤트 기반 대기로 전환
  • MAT-311 — 잡버그 묶음
    • MAT-459 — useFcmToken 미사용 listener ref 제거
    • MAT-460 — LoginScreen 소셜 로그인 핸들러 unhandled rejection 차단
    • MAT-461 — serializeJSONToHTML JSON.parse 페일세이프
    • MAT-462 — PointingScreen render body 사이드이펙트 제거

MAT-463 (CalendarModal 닫기 버튼 위치 보정) 은 디자이너 디자인 전달 후 별도 PR로 처리. 본 PR 에서 제외.

Changes

  • MAT-454 apps/native/src/hooks/useFcmToken.ts — APNs 토큰, FCM 토큰, 등록 완료 로그, 포그라운드 메시지 페이로드 4건을 __DEV__ 가드. 진단성 warn/error 는 보존.
  • MAT-455 packages/pointer-content-renderer/src/native/ContentWebView.tsx — RN WebView 의 originWhitelist 미매칭이 차단이 아니라 Linking.openURL fallback 으로 이어지는 동작 때문에 originWhitelist=['*'] 를 통과 layer 로 유지. 실제 navigation 정책은 onShouldStartLoadWithRequest 에서 deny-by-default 로 강제한다. 허용 대상은 production file:///about:blank, dev Metro 자산 http(s)://*:8081/assets/... 뿐이며, data: 및 외부 http(s) navigation 은 차단.
  • MAT-456 packages/pointer-content-renderer/src/web/index.htmlkatex.min.css / katex.min.js / kopubbatang.min.css 3개에 integrity="sha384-..." + crossorigin="anonymous". defer + __katexLoaded/__katexFailed 시그널 보존.
  • MAT-457 useSocialLoginCallback.ts 파일 삭제 + hooks/index.ts 배럴 export + RootNavigator.tsx 호출 제거. PR-4 의 useNativeOAuth 가 인앱 OAuth 를 처리하므로 url scheme callback 흐름은 더 이상 도달 불가.
  • MAT-458 services/navigation/navigationRef.tsreadySubscribers/stateSubscribers fan-out, handleNavigationReady, handleNavigationStateChange, waitForNavigationReady(timeoutMs = 30_000), waitForRouteRegistered('StudentApp', timeoutMs = 30_000) 추가. useDeepLinkHandler.tswhile 폴링(3s) 을 이벤트 기반 대기로 교체하고, 콜드스타트 auth hydration 후 StudentApp root route 등록까지 기다린 뒤 딥링크를 dispatch.
  • MAT-459 useFcmToken.ts — 선언만 되고 미사용이던 notificationListener / responseListener ref 두 개 제거.
  • MAT-460 LoginScreen.tsxhandleSocialButtonPress async → sync. void signInWithProvider(provider) 로 unhandled rejection 차단. 에러는 이미 hook 의 error state 로 화면에 노출 중.
  • MAT-461 serializeJSONToHTML.ts — string input 일 때 JSON.parse 만 좁게 try/catch 로 감싸 serializeJSONToHTML: invalid JSON input — <원인> 형태로 throw. MAT-451 (pointer-content-renderer) 와 동일 패턴.
  • MAT-462 PointingScreen.tsx — render body 의 if (pointings.length === 0) console.warn(...) 사이드이펙트 제거.

추가로 lint pass + post-review 후속 픽스:

  • chore: useDeepLinkHandler.ts 의 사전 존재하던 미사용 useCallback import 제거.
  • fix: handleNavigationReadyclear() 제거 (Fast Refresh / NavigationContainer 재mount 시 두 번째 onReady 사이 등록된 subscriber 손실 방지).
  • fix: dev Metro HTML 자산이 http://<LAN-IP>:8081/assets/... 로 resolve될 때 Safari 로 fallback 되는 문제를 originWhitelist 통과 layer + shouldAllowRequest dev Metro asset allow 로 해결.
  • fix: waitForNavigationReady / waitForRouteRegistered 의 timer cleanup 을 optional guard 로 정리해 초기화 전 접근 가능성을 제거.

Testing

  • pnpm lint --filter native — exit 0
  • pnpm tsc --noEmit (apps/native, packages/pointer-content-renderer) — exit 0
  • QA: 앱 종료 상태에서 알림 탭 → 콜드스타트 (저사양 기기, >3s) → auth hydration 후 딥링크 정상 처리
  • QA: 백그라운드/포그라운드 알림 탭 → 즉시 처리
  • QA: WebView 가 외부 도메인 (예: https://evil.example) 또는 data: navigation 을 시도할 때 WebView 내부 이동/Safari fallback 없이 차단 + dev 콘솔에 [ContentWebView] blocked navigation 로그
  • QA: dev client/Metro 환경에서 PointerContentView 가 Safari 로 튕기지 않고 앱 안에서 content.html 렌더링
  • QA: KaTeX 렌더링 정상 (SRI hash mismatch 시 fallback 동작)
  • QA: 카카오/구글/Apple 로그인 정상 동작 + 실패 시 화면에 에러 표시
  • QA: 문제 풀이 화면에서 malformed JSON 주입 시 명시적 에러 throw

Risk / Impact

  • 영향 범위: 학생 앱 부팅 알림 처리 / WebView 콘텐츠 렌더 / 소셜 로그인 흐름 / 문제 풀이 시리얼라이저
  • 호환성: useSocialLoginCallback 삭제는 인앱 url scheme OAuth callback 경로를 끊지만, PR-4 머지 이후 도달 불가 경로. 앱 내 화면 이동용 url scheme (pointer://qna/<id>, pointer://publish/<id>) 은 useDeepLinkHandler 에서 그대로 처리.
  • WebView policy: originWhitelist=['*'] 는 보안 허용 목록이 아니라 RN WebView 의 외부 앱 fallback 을 막기 위한 통과 layer. 실제 보안 경계는 shouldAllowRequest 의 deny-by-default navigation policy.
  • Known residual risk: deprecated ProblemViewer 는 scrap 경로에서 아직 사용 중이며 이번 PR 에서는 추가 수정하지 않는다. 남은 사용처는 별도 branch 에서 PointerContentView 로 migration 예정.
  • 머지 순서: PR-4 ([refactor/MAT-305-309] 인증 레이어 안정화 및 정리 #309), PR-5 ([fix/MAT-308] pointer-content-renderer 상태/리스너 누수 정리 #310) 머지 완료 후. develop 최신 기준.

sterdsterd added 11 commits May 3, 2026 20:43
프로덕션 빌드에서 디바이스 FCM 토큰과 푸시 페이로드가 콘솔에
평문 출력되어 토큰 탈취 시 임의 푸시 알림 발송 위험. APNs/FCM 토큰
log, 등록 완료 log, foreground 메시지 페이로드 dump 모두 __DEV__
가드로 감쌌다. 진단용 warn/error 는 유지.

Refs: MAT-454
WebView 가 originWhitelist={['*']} 로 모든 origin 으로 navigation 을
허용해 오픈 리다이렉트에 취약. 번들 HTML 외 외부 navigation 을
file:// / about:blank / data: 만 허용하도록 좁히고, 추가로
onShouldStartLoadWithRequest 에서 블록리스트 외 요청을 명시적으로
차단해 defense-in-depth 적용. dev 빌드는 localhost 디버그 자산도
허용. CSS/JS subresource (jsdelivr KaTeX) 는 originWhitelist 의
적용 대상이 아니므로 정상 로드 유지.

Refs: MAT-455
jsdelivr CDN 으로 가져오는 katex CSS/JS 와 font-kopub CSS 가 변조
되어도 탐지 불가능했던 무결성 결함. 실제 자원에서 산출한 SHA-384
integrity 와 crossorigin="anonymous" 추가. KaTeX 0.16.9 / font-kopub
1.0 핀 버전 그대로 유지하므로 hash 갱신 트리거는 버전 bump 시점에
한정.

Refs: MAT-456
PR-4 에서 useNativeOAuth 로 인앱 SDK 기반 소셜 로그인이 도입되며
url scheme 콜백 (exp:// / pointer://auth/callback) 흐름은 더 이상
도달 불가. 그대로 두면 getInitialURL().then 미catch 의 unhandled
rejection 위험과 addEventListener 와의 race condition 만 남는다.
hook 파일 삭제 + RootNavigator 호출 제거 + hooks barrel export 정리.
앱 내부 라우팅용 url scheme 처리 (예: 알림 딥링크) 는 useDeepLinkHandler
에서 별도 담당하므로 영향 없음.

Refs: MAT-457
저사양 기기에서 콜드 스타트가 3s 를 넘으면 busy-wait polling 이
timeout 되어 알림 딥링크가 무시되던 문제. 100ms 간격 polling 을
NavigationContainer onReady fan-out 에 연결된 module-level
subscriber 로 교체하고, 기본 timeout 을 30s 로 확장.

navigationRef 자체의 addListener('state', ...) 는 attach 전에
호출하면 throw 하므로 App 의 NavigationContainer onReady prop 을
단일 ready 신호 지점으로 사용 (handleNavigationReady).

Refs: MAT-458
선언만 되고 어떤 subscription 도 할당되지 않은 채 cleanup 도 없는
notificationListener / responseListener ref 제거. expo-notifications
의 알림 응답 처리는 useDeepLinkHandler 가 담당하고 있어 중복으로
ref 를 채울 필요가 없다.

Refs: MAT-459
handleSocialButtonPress 가 async 로 선언돼 Pressable onPress 에
Promise 를 반환했고, signInWithProvider 의 reject 가 unhandled
rejection 으로 새어 나갈 수 있었다. 핸들러를 sync 로 바꾸고
void signInWithProvider(provider) 형태로 명시. 사용자 가시 에러는
useNativeOAuth 의 error state 로 이미 surface 되므로 silent
fallback 우려 없음.

Refs: MAT-460
string 입력 분기에서 JSON.parse 가 try/catch 없이 호출돼 malformed
콘텐츠가 들어오면 호출자 (ProblemViewer) 까지 그대로 throw 가
전파되어 화면 전체가 크래시되던 결함. parse 단계만 try/catch 로
감싸고 발생 detail 을 포함한 새 Error 로 재던져 디버깅을 쉽게 함.
pointer-content-renderer 의 동명 함수 (MAT-451) 와 동일 패턴.

Refs: MAT-461
빈 pointings 배열을 감지하던 console.warn 이 render 함수 내부에
있어 React pure render 원칙을 위반하고 매 렌더마다 동일한 경고가
중복 출력되는 노이즈 결함. 진단 로그가 실질적으로 가치를 제공하지
못해 단순 삭제 (Linear 와 사용자 결정).

Refs: MAT-462
PR-7 lint pass 마무리. 사전 존재하던 unused import 제거.
Fast Refresh / NavigationContainer 재mount 시 onReady 가 재발화될 수 있어
clear() 를 하면 두 번째 mount 사이에 등록된 subscriber 가 손실된다.
finish() 안에서 자기 자신을 delete 하므로 leak 없이 정리됨.

OMC code-reviewer MEDIUM 후속 픽스.
@linear
Copy link
Copy Markdown

linear Bot commented May 3, 2026

@vercel
Copy link
Copy Markdown

vercel Bot commented May 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pointer-admin Ready Ready Preview, Comment May 4, 2026 3:43pm

prettier/prettier — multi-line type import 가 100자 width 안에 들어가므로
single-line 으로 강제됨. CI lint fail 픽스.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

학생 앱(네이티브)에서 보안 가드(WebView navigation 차단/FCM 로그 가드/CDN SRI)와 딥링크 콜드스타트 안정화, OAuth dead code 제거, 몇 가지 런타임 잡버그를 한 PR로 묶어 정리합니다.

Changes:

  • Content WebView 보안 강화: originWhitelist 축소 + onShouldStartLoadWithRequest로 외부 navigation 차단, KaTeX/폰트 CDN에 SRI 추가, FCM 민감 로그 __DEV__ 가드
  • 딥링크 콜드스타트 안정화: navigation ready 대기 로직을 polling → NavigationContainer.onReady 기반 fan-out 대기로 전환
  • dead code/잡버그 정리: useSocialLoginCallback 제거, LoginScreen unhandled rejection 방지, serializer JSON.parse 페일세이프, render body side-effect 제거

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/pointer-content-renderer/src/web/index.html KaTeX/폰트 CDN 리소스에 SRI + crossorigin 추가
packages/pointer-content-renderer/src/native/ContentWebView.tsx originWhitelist 축소 및 navigation 차단 로직 추가
apps/native/src/services/navigation/navigationRef.ts navigation ready fan-out(waitForNavigationReady/handleNavigationReady) 추가
apps/native/src/services/navigation/index.ts 새 navigation 유틸 re-export
apps/native/src/navigation/RootNavigator.tsx useSocialLoginCallback 호출 제거
apps/native/src/hooks/useSocialLoginCallback.ts (삭제) OAuth scheme callback 훅 제거
apps/native/src/hooks/useFcmToken.ts FCM/APNs 토큰 및 페이로드 로그를 __DEV__로 가드
apps/native/src/hooks/useDeepLinkHandler.ts navigation ready 대기를 waitForNavigationReady 기반으로 변경
apps/native/src/hooks/index.ts 배럴 export에서 useSocialLoginCallback 제거
apps/native/src/features/student/problem/utils/serializeJSONToHTML.ts string input JSON.parse 오류를 명시적 에러로 래핑
apps/native/src/features/student/problem/screens/PointingScreen.tsx render body의 console.warn side-effect 제거
apps/native/src/features/auth/login/screens/LoginScreen.tsx 소셜 로그인 호출을 void 처리해 unhandled rejection 방지
apps/native/App.tsx NavigationContainer에 onReady={handleNavigationReady} 연결

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/pointer-content-renderer/src/native/ContentWebView.tsx Outdated
Comment thread packages/pointer-content-renderer/src/native/ContentWebView.tsx Outdated
Expo dev 빌드는 require('./*.html') 자산을 Metro bundler 가
http://<LAN-IP>:8081/... 로 서빙한다. originWhitelist 가 file:// 만 허용해
해당 URL 이 차단되었고, RN WebView 가 외부 핸들러로 fallback 하면서
Safari 가 열리고 컨텐츠가 보이지 않던 문제 수정.

- __DEV__ 일 때 originWhitelist 에 http://, https:// 포함
- onShouldStartLoadWithRequest 의 dev 예외를 모든 host 로 확장
- 프로덕션 빌드는 file:// + about:blank 만 허용 (보안 가드 유지)
PR-7 코드 리뷰 후속 픽스. 두 가지 우려 반영.

1. ContentWebViewHtmlSource 가 WebViewSource (외부 https uri 포함) 까지
   허용해 API/실제 동작 (originWhitelist 가 file:// 만 허용) 이 불일치했다.
   타입을 ImageRequireSource | { html: string } 으로 좁혀 의도와 일치시킴.
2. shouldAllowRequest 에서 data: URL 무조건 허용 → 임의 HTML/JS 실행
   우회 경로가 됨. data: 분기 제거. RN WebView 의 injectedJavaScript /
   postMessage 는 navigation 이 아니라 영향 없음.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/pointer-content-renderer/src/native/ContentWebView.tsx Outdated
Comment thread packages/pointer-content-renderer/src/native/ContentWebView.tsx
Comment thread apps/native/src/services/navigation/navigationRef.ts Outdated
sterdsterd added 2 commits May 3, 2026 23:09
…으로 일원화

PR-7 코드 리뷰 후속. originWhitelist 가 미스매치 시 외부 앱 (Linking) 으로
fallback 하는 RN WebView 동작 때문에, 이전 구현은 fail-open 경로가 있었다.

- originWhitelist 를 ['*'] 로 두고 통과 layer 로만 사용
- onShouldStartLoadWithRequest 에서 deny-by-default 로 정책 강제
- file://, about:blank, dev Metro asset (port 8081 + /assets/*) 만 허용
- dev http(s) allow-all 도 함께 좁혀 외부 redirect 는 dev 에서도 차단됨
PR-7 코드 리뷰 후속 (P1).

RootNavigator 가 sessionStatus 에 따라 단일 root screen (Splash | Auth |
StudentApp) 만 등록하므로, 알림으로 종료 상태에서 앱이 켜질 때
sessionStatus === 'hydrating' 단계에서는 Splash 만 navigator 에 존재한다.
이때 navigationRef.isReady() 가 true 여도 'StudentApp' 으로의
CommonActions.navigate 가 silent no-op 이 되어 딥링크가 유실된다.

수정:
- navigationRef 에 stateSubscribers + handleNavigationStateChange 추가,
  App.tsx 의 NavigationContainer onStateChange prop 에 연결.
- waitForRouteRegistered(routeName, timeoutMs) helper 추가 — onReady +
  onStateChange 둘 다 구독해 원하는 route 가 등록될 때까지 대기.
- useDeepLinkHandler 가 waitForNavigationReady 다음에 'StudentApp' 등록을
  추가로 기다린 뒤 dispatch.
- 곁들여 waitForNavigationReady 의 finish/handler/timer ordering 정리
  (timer 를 let 으로 선언 후 할당 — 미래 편집 시 TDZ 위험 제거).
prettier — 100자 width 초과 문자열을 multi-line 으로 강제. CI format:check 픽스.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/pointer-content-renderer/src/native/ContentWebView.tsx Outdated
Comment thread packages/pointer-content-renderer/src/native/ContentWebView.tsx
Comment thread apps/native/src/services/navigation/navigationRef.ts Outdated
Comment thread apps/native/src/services/navigation/navigationRef.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +52 to +56
const studentAppReady = await waitForRouteRegistered('StudentApp');
if (!studentAppReady) {
console.warn(
'[DeepLink] StudentApp route 미등록 — 알림이 unauthenticated 상태에 도착했을 가능성'
);
@sterdsterd sterdsterd merged commit 1715647 into develop May 4, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 Fix 버그 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants