docs: record mobile production audit

This commit is contained in:
Yun Chan 2026-08-30 09:37:40 +09:00
parent 1f71cf6fae
commit 784c9c291e

View file

@ -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 <yunchan@twentyoz.kr>`, 메시지는
`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 제출을 금지한다.