/* * 기기 무결성/위변조 탐지 레이어 (B1 루트·탈옥 / B9 디버거·개발자모드·USB / B2 앱무결성 토큰 전송부). * 근거: docs/security/mobile-security-audit.md 백로그 B1·B2·B9, checklist AT-1/AT-4/AT-6/AT-2. * * 성격(공공 대민 정책): 탐지 시 "경고 후 제한" — 하드 차단이 아니라 사용자 고지. * 오탐(에뮬레이터 개발기기·루팅 오검출)으로 정상 이용자를 잠그지 않는다(graceful). * 도입 경로: jail-monkey는 네이티브 모듈 → Expo Go/웹/미설치 기기에서는 방어적 동적 require로 * "탐지 불가(안전 가정)" 취급. dev-client/prebuild(EAS)에서 실제 동작. (biometric.ts 동일 패턴) * 시크릿 미보관: 앱은 불린/토큰만 다루고 원본 서명·키를 저장하지 않는다. */ import { useEffect, useState } from 'react'; // eslint-disable-next-line @typescript-eslint/no-explicit-any let JailMonkey: any = null; try { // eslint-disable-next-line @typescript-eslint/no-var-requires JailMonkey = require('jail-monkey'); // 일부 번들러는 default 래핑 → 안전 정규화 if (JailMonkey && JailMonkey.default) JailMonkey = JailMonkey.default; } catch { JailMonkey = null; } export interface IntegrityResult { /** 네이티브 탐지 모듈 사용 가능 여부(불가 시 아래 값은 모두 안전 기본값 false). */ available: boolean; /** 루팅(Android)·탈옥(iOS) 단말. */ jailBroken: boolean; /** 디버거 attach(Android). */ debugger: boolean; /** 후킹(Frida/Xposed 등, Android). */ hooking: boolean; /** USB 디버깅(ADB) 활성(Android). */ adbEnabled: boolean; /** 개발자 옵션 활성(Android). */ devMode: boolean; /** 위치 모의(mock) 허용. */ mockLocation: boolean; /** 종합 위변조 신호(위 항목 중 하나라도 참). */ compromised: boolean; } const SAFE: IntegrityResult = { available: false, jailBroken: false, debugger: false, hooking: false, adbEnabled: false, devMode: false, mockLocation: false, compromised: false, }; /** 동기 불린 호출을 방어적으로 실행(예외/미구현 시 false). */ function safeBool(fn: (() => boolean) | undefined): boolean { if (typeof fn !== 'function') return false; try { return !!fn(); } catch { return false; } } /** Promise 반환 API를 방어적으로 실행(예외/미구현 시 false). */ async function safeBoolAsync(fn: (() => Promise) | undefined): Promise { if (typeof fn !== 'function') return false; try { return !!(await fn()); } catch { return false; } } /** * 기기 무결성 점검. 네이티브 모듈이 없으면 SAFE(모두 false)로 graceful 반환. * 결과는 UI에서 "경고 배너" 등으로 활용(차단 아님). */ export async function checkDeviceIntegrity(): Promise { if (!JailMonkey) return { ...SAFE }; try { const jailBroken = safeBool(JailMonkey.isJailBroken); const hooking = safeBool(JailMonkey.hookDetected); const adbEnabled = safeBool(JailMonkey.AdbEnabled); const mockLocation = safeBool(JailMonkey.canMockLocation); const debugger_ = await safeBoolAsync(JailMonkey.isDebuggedMode); const devMode = await safeBoolAsync(JailMonkey.isDevelopmentSettingsMode); const compromised = jailBroken || hooking || debugger_; return { available: true, jailBroken, debugger: debugger_, hooking, adbEnabled, devMode, mockLocation, compromised, }; } catch { return { ...SAFE }; } } /** * React 훅 — 마운트 시 1회 무결성 점검(비차단). 로그인 등 진입 화면에서 경고 노출용. */ export function useDeviceIntegrity(): IntegrityResult { const [result, setResult] = useState(SAFE); useEffect(() => { let cancelled = false; checkDeviceIntegrity().then((r) => { if (!cancelled) setResult(r); }); return () => { cancelled = true; }; }, []); return result; } // ── B2 앱 무결성 토큰(전송부만) ─────────────────────────────────────────────── /* * Play Integrity(Android)·App Attest(iOS) 토큰은 각 플랫폼 네이티브 모듈로 "발급"한다. * 본 앱은 발급기(provider)를 주입받아 "전송"만 담당한다(api.ts가 헤더로 첨부). * 서버 검증 엔드포인트는 backend-dev 인계(_workspace/08_mobile_security_hardening.md). * provider 미등록 시 getAttestationToken()은 null → 헤더 미첨부(무해). */ type AttestationProvider = () => Promise; let attestationProvider: AttestationProvider | null = null; /** 네이티브 무결성 토큰 발급기 등록(추후 Play Integrity/App Attest 모듈 연동 시). */ export function setAttestationProvider(provider: AttestationProvider | null): void { attestationProvider = provider; } /** 등록된 발급기로 무결성 토큰 조회. 미등록/실패 시 null(graceful). */ export async function getAttestationToken(): Promise { if (!attestationProvider) return null; try { return await attestationProvider(); } catch { return null; } }