Đưa một app két riêng tư qua App Review sau các lần dính 2.5.1
Lockboxy đi từ ba lần bị từ chối theo guideline 2.5.1 đến lúc được duyệt: đổi màn hình mặc định, rà lại onboarding, viết lại listing, screenshot, review notes.

Trong bài này
Lockboxy là app két mã hoá của mình trên iPhone: ảnh, video, file, mật khẩu, ghi chú đều được mã hoá ngay trên máy, sau một passcode chỉ chủ máy biết. Để bản đầu tiên lên được App Store, mình mất từ tháng 7 đến đầu tháng 9/2026. Guideline 2.5.1 (Software Requirements) bị nêu trong ba lá thư chỉ trong một tuần, rồi còn quay lại thêm ba lần nữa sau khi mình tưởng đã sửa xong.
Bài này kể lại cụ thể những gì đã thay đổi giữa các lá thư đó và lần được duyệt, dựa trên working log mình ghi ngay trong repo lúc chuyện đang diễn ra. Không có cách sửa nào thông minh cả. Gần như tất cả quy về một chuyện: build, listing và screenshot phải mô tả cùng một app.
Các lần bị từ chối, và cái sai đầu tiên của mình
Bản đầu tiên được submit ngày 21/7. Trong năm ngày sau đó có bốn lá thư. 2.5.1 nằm trong ba lá, đi kèm 3.1.2(c) (link EULA chỉ có trong mô tả tiếng Việt, bản tiếng Anh thì không có) và 2.1(b) (reviewer không tìm thấy in-app purchase). Lá thư 2.5.1 nào cũng cùng một câu chữ. Nó gắn finding với Photos API và với cách app giới thiệu tính năng ảnh chính của mình.
Cái sai đầu tiên của mình xảy ra trước cả khi đọc kỹ thư. Mình đinh ninh vấn đề là màn hình máy tính trông quá giống Calculator của Apple, nên đã làm lại toàn bộ: phím mới, bảng màu mới, bố cục mới. Đến lúc mở từng lá thư trong App Store Connect thì không lá nào nói gì về giao diện máy tính cả. Bản redesign vẫn đáng giữ, vì skin cũ đúng là gần như chép từng pixel của iOS Calculator, nhưng nó trả lời một finding mà Apple chưa từng viết ra.
Đọc chính lá thư trong App Store Connect trước khi quyết định sửa gì. Tóm tắt theo trí nhớ, hay nghe người khác kể lại, là cách nhanh nhất để mất trắng một vòng review.
Điều thứ hai mình nhận ra: theo những gì mình thấy, Apple không cần chạy app cũng viết được lá thư 2.5.1. Listing đã tự nói hết: phần mô tả và keyword mở đầu bằng màn hình máy tính thay vì cái két. Ba lần sửa chỉ trong code không thể nào gỡ được finding đó.
Thay đổi 1: màn hình mặc định
Ngày 11/8, mình biến chuyện này thành một thử nghiệm có thể hoàn tác và làm đúng theo lời Apple. App giờ mở vào một màn hình khoá bình thường: bàn phím 4 số, Face ID và một link "Forgot your passcode?". Mọi thứ xây quanh màn hình máy tính bị gỡ khỏi routing và Settings. Còn database schema, key slot và mọi đường crypto thì giữ nguyên. Thử nghiệm để qua review là lý do tệ nhất để mổ xẻ phần lõi bảo mật.
Có một chi tiết mình suýt bỏ sót: Recovery Key (tuỳ chọn) là chuỗi 80 chữ số, và trước thay đổi này chỉ gõ được trên bàn phím máy tính. Bàn phím khoá 4 số thì không nhập được, nên sau "Forgot your passcode?" phải có một ô nhập thật. Thiếu ô đó, ai quên mã PIN là mất két vĩnh viễn. Khi gỡ một cửa vào, hãy kiểm tra xem còn thứ gì khác chỉ đi qua được cửa đó.
Thay đổi 2: cả listing, không chỉ phần chữ
Ngay trong ngày, mình viết lại name, subtitle, description và keyword cho cả tiếng Anh lẫn tiếng Việt. Câu mở đầu mô tả đổi thành "Lockboxy is a private, encrypted vault that lives on your device". Keyword "hide photos" bị bỏ, cùng mọi từ mô tả app như một thứ khác ngoài két.
Build 12 qua được 2.5.1 vào ngày 12/8, nhưng có hai finding hẹp hơn thế chỗ:
- 5.1.1(ii): purpose string của camera đúng nhưng mơ hồ, và không hề nhắc đến việc chụp bằng camera trước của Intruder Selfie. Mình viết lại cả chuỗi camera lẫn microphone, nêu rõ từng chỗ dùng.
- 2.3: reviewer không tìm thấy trong app một màn hình mà screenshot đang quảng cáo.
Cái thứ hai là lỗi của mình. Checklist chỉ ghi "name, subtitle, description, keywords" mà quên mất screenshot, trong khi screenshot mới là phần "to tiếng" nhất của listing. Metadata trên App Store còn gồm promotional text, screenshot và app preview của từng localization, và bộ iPad tách riêng với bộ iPhone. Lần review đó chạy trên iPad. Mình còn phải ra một build chỉ để hai chuỗi trong app khớp với bộ screenshot vừa gửi, vì "screenshot không khớp với app" chính là điều 2.3 nói.
Thay đổi 3: onboarding, chỗ đã rà mà vẫn sót
Ngày 13/8, 2.5.1 quay lại. Lá thư lặp nguyên văn bản tháng 7 và trường Review Device để trống. Thay vì đoán thêm một lần sửa code thứ bảy, mình trả lời trong Resolution Center bằng bốn sự thật có thể kiểm chứng trong code, rồi hỏi thẳng: tính năng nào, ở màn hình nào, gọi Photos API nào. Câu trả lời bỏ hẳn 2.5.1 và chỉ ra một bug thật: nút Share trong trình duyệt riêng không phản hồi khi tab đang trống. Nút bị disable, và dấu hiệu duy nhất là màu đổi nhẹ. Mình sửa để nút lúc nào cũng phản hồi bằng một thông báo ngắn.
Ngày 16/8, 2.5.1 lại về. Lần này trường Review Device có ghi thiết bị, nghĩa là họ đã chạy binary. Thủ phạm là feature tour lúc mở app lần đầu. Mình đã dọn phần chữ của tour, nhưng các ô icon trong tour dùng lại locale key của những dòng Settings đã xoá. Key vẫn còn trong file locale, nên tour vẫn compile và vẫn hiện ô của những tính năng không còn tồn tại, ngay trên màn hình đầu tiên reviewer nhìn thấy. Grep theo route thì sạch trơn, vì chẳng có route nào dính vào.
Gỡ một tính năng nghĩa là rà mọi chỗ gọi tên nó, chứ không chỉ mọi chỗ route tới nó. Bản sửa có thêm một regression test, làm build fail nếu tên tính năng đã gỡ xuất hiện lại trong tour.
Thay đổi 4: review notes chỉ reviewer cách vào app
Một app két không có tài khoản demo sẽ rất khó review nếu không ai giải thích. Bản notes mình chốt gồm ba phần ngắn:
1) WHAT CHANGED IN THIS BUILD
2) HOW TO OPEN THE APP
No demo passcode. On first launch you create your own 4-digit passcode,
confirm it, then enter it on the lock screen.
3) HOW TO FIND THE IN-APP PURCHASES
Settings tab > "Go Premium" card. All three products are listed there.Vòng cuối lại là 2.1(b). Ảnh chụp đính kèm lá thư cho thấy một lỗi tài khoản sandbox của App Store, đè lên một paywall đã load sản phẩm bình thường. Mình trả lời đúng điều đó, kèm một giao dịch đã tái hiện thành công trên chính storefront của reviewer, và thêm mã lỗi StoreKit hiện trên màn hình ở build 17. Bản 1.0.1 được phát hành ngày 9/9.

Đưa opening screen trở lại, với điều kiện khác
Khi 1.0.1 đã live, ở bản 1.2.0 mình đưa máy tính trở lại dưới dạng một opening screen tuỳ chọn. Lần này luật chơi ngược hẳn:
- Mặc định tắt. Cài mới thì app mở vào màn hình khoá.
- Người dùng tự chọn trong Settings › Security › "Opening screen", và tắt lại cũng ngay màn hình đó, một chạm.
- Giải thích trước khi bật. Mỗi lựa chọn đều nói rõ nó hoạt động thật và gọi đúng tên ô nhập passcode. Có unit test fail nếu đoạn giải thích bị cắt bớt, và VoiceOver đọc cả phần giải thích chứ không chỉ tiêu đề.
- Không khoá sau Premium, để reviewer vào được mà không cần mua sandbox.
Listing mô tả đúng như vậy, trong mục riêng "OPENING SCREEN (OPTIONAL)": "It is off unless you turn it on, under Settings › Security › "Opening screen"." Bản 1.2.6 qua review với câu chữ này, và từ đó mình giữ nguyên nó cho mọi locale. Sau này mình cũng sửa Terms trong app cho khớp.

Những điều mình sẽ làm lại
- Đọc chính lá thư, và để ý trường Review Device có ghi gì không. Thư lặp lại với Review Device trống thì hỏi một câu cụ thể. Có ghi thiết bị thì soi binary kỹ hơn.
- Coi listing là một phần của build: name, keyword, description, promotional text, screenshot và preview, ở mọi locale và mọi loại thiết bị.
- Grep theo tên của mọi thứ mình gỡ, và gắn test cho nó.
- Tự khai báo những tính năng có rủi ro trong review notes và đề nghị gỡ nếu cần. Một bản sau có một tính năng mặc định tắt có đụng tới thư viện ảnh; notes mô tả nó và đề nghị gỡ nếu Apple không đồng ý, và bản đó vẫn được duyệt.
Bạn có thể xem Lockboxy tại lockboxy.io.vn và các app khác của mình tại apps.vanthuongdao.id.vn. Phần mã hoá, tức passcode thực sự bảo vệ cái gì, nằm ở bài Mã hoá trên máy trong một app két React Native.
Bài liên quan


