From 784c9c291eed07561f4f335e760c12065cf88b60 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Sun, 30 Aug 2026 09:37:40 +0900 Subject: [PATCH] docs: record mobile production audit --- docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md | 157 +++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md b/docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md index 3cbd43a..5e6983f 100644 --- a/docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md +++ b/docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md @@ -731,3 +731,160 @@ Google Play의 현재 공식 요구는 휴대전화 screenshot의 최소 변 320 Firebase 생성, GitLab variable 변경, Play 업로드, legal declaration, tag/public release는 이번 감사에서 실행하지 않았다. + +## 15. 2026-08-30 승인 후 실행과 production 판정 + +이 절은 14절 작성 뒤 사용자가 명시적으로 승인한 작업과 병렬 production 감사를 +기록한다. 14절의 승인 전 스냅샷과 충돌하면 이 절을 우선한다. + +### 15.1 승인 후 실제로 실행한 것 + +- 다음 세 파일만 stage해 `origin/main`에 push했다. commit은 + `1f71cf6faeb209c85b49a0260312897a25cc41d9`, 작성자는 + `Yun Chan `, 메시지는 + `ci: allow protected mobile candidate`다. + - `.gitlab-ci.yml` + - `scripts/ci/verify-mobile-release-boundary.mjs` + - `docs/v3/MOBILE_RELEASE_HANDOFF_2026-08-29.md` +- 실제 `origin/main`도 `1f71cf6faeb209c85b49a0260312897a25cc41d9`로 + 확인했다. unrelated dirty worktree는 stage/reset/clean하지 않았다. +- Firebase production project를 생성했다. + - project ID: `d3ro-voice-production` + - project number: `1013351450402` + - display name: `D3RO Voice Production` +- Android app을 등록했다. + - package: `com.d3ro.voice` + - app ID: `1:1013351450402:android:4d9d0f675b12ee09bf477b` + - display name: `D3RO Voice Android` +- `apps/mobile-rn/android/app/google-services.json`을 내려받아 project/package/app + identity를 검증했다. 실제 API key는 출력하지 않았고 파일은 gitignored다. +- production Firebase/AdMob 기대값으로 `npm run release:mobile:config`를 실행해 + project/package/client/API-key uniqueness와 production ad unit 형식 검사를 + GREEN으로 확인했다. +- Firebase Android app의 SHA fingerprint 목록은 아직 0개다. local upload + certificate의 SHA-1은 + `AE:2C:60:EB:9E:E3:06:FF:4D:36:D9:17:5C:F1:C6:67:CA:4A:8A:F8`, + SHA-256은 + `4F:AC:69:24:82:1C:50:DA:AB:ED:76:49:32:A5:3C:48:6F:8C:6C:5F:34:B9:F1:8D:B9:20:AA:40:99:15:2B:54`다. + Play app-signing SHA와 upload SHA를 구분해 등록해야 하므로 임의 등록하지 않았다. +- 인앱 브라우저에 Play Console account selector, GitLab 로그인, Firebase Android + settings 탭을 visible 상태로 열어 뒀다. Play 개인 계정 선택과 GitLab 로그인은 + 아직 필요하다. +- Play AAB 업로드, CI secret 변경, policy/legal declaration, stable tag/public + release, Forgejo push/deploy는 실행하지 않았다. + +### 15.2 병렬 production 감사 결론 + +최종 판정은 **NO-GO / production level 아님**이다. Android 소스는 단순 +prototype이 아니라 후기 beta 또는 RC 후보에 가깝지만, 실제 배포·보안·운영 +경계가 닫히지 않았다. + +| 영역 | 판정 | 현재 증거 | +| ---------------------------------- | ------- | ------------------------------------------------------------------------- | +| 정적 품질 | GREEN | ESLint 0, typecheck GREEN, Jest 59 suites / 369 tests GREEN | +| Android 기본 release config | PARTIAL | API 36, release cleartext/backup 차단, Firebase/AdMob/signing fail-closed | +| production artifact / Play install | RED | production AAB 0개, Play-signed Fold6 설치 증거 없음 | +| 인증·계정·로컬 데이터 경계 | RED | 아래 HIGH 결함 존재 | +| OAuth·FCM·Billing·AdMob 실제 E2E | RED | production provider/Play 설치본 증거 없음 | +| production backend | RED | migration 4개 및 `content-report` 배포 누락 | +| 운영 관측성 | RED | Crashlytics/Sentry/Bugsnag, JS ErrorBoundary, native exception hook 없음 | +| 접근성·복원력·Fold matrix | RED | TalkBack/200%/offline/5xx/token expiry/process death/권한 철회 등 미완료 | +| Play 제출 자산·접근 | RED | 유효 screenshot 0장, 계정에 따라 12명/14일 closed test 필요 | +| iOS | RED | native release config, build/device/provider 증거 없음 | + +병렬 실행 부하에서 admin Jest 2건이 5초 timeout을 냈지만 isolated와 전체 순차 +재실행은 GREEN이었다. 기능 실패로 단정하지 않되 CI 환경의 flake/부하 민감성은 +별도 정리한다. + +### 15.3 새로 발견한 출시 차단급 보안·데이터 경계 + +1. **인증 callback 탈취/로그인 CSRF 경계** + - Android auth callback은 검증되지 않은 + `d3ro-voice://auth-callback` custom scheme이다. + - `apps/mobile-rn/src/lib/auth-redirect.ts`는 pending state/PKCE 상관관계 없이 + fragment의 access/refresh token pair를 `setSession`에 전달한다. + - code-only PKCE, claimed HTTPS App Link, pending state 검증으로 닫기 전에는 + production 로그인 경계를 GREEN으로 보지 않는다. +2. **계정 삭제의 잘못된 최근 재인증 판정** + - `server/supabase/functions/account-delete/index.ts`는 access JWT의 `iat`가 + 10분 이내면 최근 재인증으로 본다. + - auto-refresh된 access JWT도 새 `iat`를 가질 수 있으므로 명시적인 사용자 + 재인증 증거가 아니다. +3. **비원자적 계정 삭제** + - 같은 함수가 storage object를 먼저 영구 삭제하고 auth user를 나중에 + 삭제한다. 후반 실패 시 데이터만 사라지고 계정은 남을 수 있다. + - durable deleting state와 idempotent saga, 각 단계 failure injection이 필요하다. +4. **개인정보처리방침과 실제 처리 불일치** + - live source 정책은 local transcription을 선택하면 기기 안에서 처리하고 + 민감 local cache를 암호화한다고 설명한다. + - 실제 import/recording 경로는 audio를 Supabase에 먼저 업로드하고 cloud STT + 실패 뒤 local fallback하며, 일부 durable/import/export/action history와 upload + checkpoint는 평문 file 또는 AsyncStorage JSON이다. + - 진짜 local-only 선택과 암호화 저장을 구현하거나 정책·Data safety를 실제 + 흐름과 정확히 맞춰야 한다. +5. **로그아웃 뒤 계정 간 local artifact 격리 누락** + - 중앙 purge가 pending native incoming media와 resumable upload checkpoint를 + 제거하지 않는다. 다음 로그인 사용자가 이전 사용자 파일 또는 checkpoint를 + 이어받을 가능성을 닫아야 한다. + - push detach가 실패하면 현재 `performAccountLogout`은 local sign-out/purge에도 + 도달하지 않는다. local logout을 먼저 보장하고 detach tombstone을 재시도하는 + 구조가 필요하다. + +추가로 최대 25 MB audio를 JS `Uint8Array`, base64, multipart에서 여러 번 복제해 +저메모리 기기 OOM/ANR 위험이 있다. streaming/native upload 또는 더 낮은 제한과 +low-RAM 실기기 profiling이 필요하다. + +### 15.4 release·backend·운영 차단 요인 + +- production AAB는 여전히 없다. 기존 `app-release.apk`는 1.0/1, 폐기 signer, + Whisper model 누락이므로 재사용 금지다. +- Fold6 설치본은 `0.0.0-dev.20260822.1`, `DEBUGGABLE`, installer `null`인 개발 + sideload다. +- production Supabase는 `20260821000028`까지만 적용됐다. 다음 네 migration은 + local에만 있다. + - `20260824000029_content_reporting.sql` + - `20260824000030_profile_managed_field_acl.sql` + - `20260825000031_audit_log_actor_unlink.sql` + - `20260829000032_meeting_document_content_reporting.sql` +- production Edge Function 목록에 `content-report`가 없다. AI response reporting과 + moderation 경로를 production 기능으로 주장하지 않는다. +- live `https://d3ro.chanpaca.net/.well-known/assetlinks.json`은 폐기 signer + `06:EE:C7:57:...:E4:81`을 반환한다. 기록된 Play app-signing SHA-256 + `01:00:19:21:...:0D:AB`와 맞지 않는다. +- 실제 Google consent→mobile callback, FCM foreground/background/terminated + receipt, Play Billing purchase/pending/cancel/restore/refund, production AdMob SSV + reward의 최신 실증이 없다. +- `npm --prefix apps/mobile-rn audit --omit=dev`는 critical 1, high 8, + moderate 11, low 1을 반환했다. critical/high 대부분은 RN CLI/Metro/ESLint/Jest + build-tool chain이며 현재 E2E Android bundle에 runtime dependency로 포함된 + 취약점이라고 과장하지 않는다. 그래도 CI 공급망 관점에서 upgrade 또는 + 비영향 근거를 남겨야 한다. +- 기존 E2E APK와 stale release APK의 16 KB ZIP alignment는 + `zipalign -c -P 16 4`를 통과했다. production AAB의 모든 ELF와 실제 16 KB + device/Play 검증을 대체하지 않는다. +- release `minifyEnabled`는 현재 false다. 즉시 제출 차단으로 단정하지 않지만, + 2027년 Play DEX optimization/shrinking/obfuscation 요구 대응 항목으로 둔다. +- 최종 Play screenshot은 0장이다. 기존 3장은 Metro 오류, 빈 화면, onboarding + 참고 캡처라 제출하지 않는다. + +### 15.5 다음 실행 순서 + +1. HIGH 인증·계정 삭제·계정 간 local data 경계를 먼저 수정하고 적대적/failure + injection 테스트를 추가한다. +2. 개인정보처리방침·Data safety와 실제 audio/cache 흐름을 하나의 계약으로 + 정렬한다. +3. production Supabase migration 4개와 `content-report` 배포를 별도 승인 아래 + 수행하고 실제 reporting/purge 운영 경로를 검증한다. +4. 올바른 Play 개인 계정과 GitLab Maintainer로 로그인해 Firebase Play/upload + fingerprint, OAuth/FCM, protected secret를 구분해 구성한다. +5. 고정 CI에서 production AAB/evidence를 만들고 signer/config/ABI/model/16 KB/ + provenance를 독립 검증한다. +6. Play internal upload 뒤 Play-signed Fold6 설치본으로 정상·실패·복구·접근성 + matrix와 OAuth/FCM/Billing/AdMob E2E를 통과한다. +7. pre-launch report와 Vitals/Crash·ANR 관측 경계를 확인한다. +8. 그 뒤 실제 사용자 UI만으로 최종 screenshot 6장을 촬영한다. +9. 적용 대상 개인 개발자 계정이면 12명 연속 14일 closed test와 production + access 승인을 완료한다. + +위 항목 전에는 공개 출시, production-ready 선언, stable tag/public release, +debug/E2E 캡처의 store 제출을 금지한다.