Stop exposing app data to the iOS Files app - #96531
Conversation
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
🚧 NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
|
🚧 NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here. |
This comment has been minimized.
This comment has been minimized.
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 959f786962
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
This PR adds a new |
|
This is WIP, will re-open when it's in a testable status |
|
Heads up on a user-facing tradeoff in this PR that could use a product opinion — cc @Expensify/Product: This PR hides the app's entire Documents folder from the iOS Files app, that includes any PDFs/documents users previously downloaded through the old flow and saved in the visible "New Expensify" folder. Those files are not migrated or surfaced anywhere — after this update they still exist on disk but become unreachable to the user. The new download flow hands files off through the share sheet ("Save to Files"), so anything downloaded going forward is unaffected. The question is only about previously downloaded files. Options:
Is option 1 acceptable, or should we handle existing downloads more gracefully? If 1, we should note it in the PR body/QA steps as expected behavior. |
|
Woof, I don't want people to get angry thinking we've lost their curated list of downloads and force them to trawl through chats, reports, threads etc to re-download them. What would it take to move them somewhere user accessible before hiding the folder? |
|
I agree with Tom. I think it's dangerous to not give them someplace obvious to find them, at least for now. This is the number one thing that our users are counting on us for, keeping their digitized receipt data. |
|
Sounds good, let me work in a hybrid proposal and try to keep the folder while keeping onyx and similar data out of sight. |
a71ba62 to
af14bb0
Compare
Disable UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace and move
internal files out of the user-visible Documents directory:
- OnyxDB moves to Library/Application Support via a react-native-nitro-sqlite
patch that also migrates existing database files
- The attachment cache moves to Library/Caches
- Queued receipt uploads move to Library/Application Support
- Non-media downloads now go through the share sheet ("Save to Files")
instead of being written to Documents, with a startup migration cleaning
up files left behind by older versions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The global react-native-fs mock lacked the functions and directory constants the MoveFilesOutOfDocuments migration uses, which crashed app-boot test suites, and AttachmentTest still expected the attachment cache under the documents directory. The migration also guards against synchronous throws so a file-system error can never block startup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… migration The migration now copies queued receipts instead of moving them, rewrites the receipt paths persisted in queued requests and transactions to the new upload folder, and only deletes the originals once the rewrite has landed. Receipts that fail to copy keep their only copy in the old directory, which is then preserved, and their persisted paths are refreshed to the current container path so the queued upload can still recover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The receipts migration only rewrote transaction receipt.source, but odometer images live in the same Receipts-Upload folder and are referenced from transaction comments (odometerStartImage/odometerEndImage), mergeTransaction entries, and the standalone odometer draft. Rewrite those references too so in-progress odometer flows survive the move out of Documents. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hiding the Documents directory entirely would make files users previously downloaded through the app unreachable. Instead, the Info.plist file-sharing keys stay and downloads keep saving to Documents, while everything internal moves to directories the Files app never shows: temporary share-sheet and video files go to Caches, the Onyx state dump goes to Caches and a stale copy in Documents is removed by the startup migration. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Queued receipt uploads are files the user expects to find in the app's Files-app folder, so they stay in Documents alongside downloads. This removes the receipt relocation and the persisted-path rewriting from the startup migration, which now only cleans up internal leftovers: the old attachment cache and a stale Onyx state dump. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
61adb7d to
9961ef4
Compare
Sounds good, thanks. 👍 |
# Conflicts: # Mobile-Expensify
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-data-exposure # Conflicts: # Mobile-Expensify
# Conflicts: # Mobile-Expensify
Explanation of Change
The iOS app enables
UIFileSharingEnabledandLSSupportsOpeningDocumentsInPlace, which publishes the entireDocuments/directory to the iOS Files app. That exposed the Onyx database, attachment caches, and receipt images to the user (and any app with Files access), all of it user-deletable — deleting the OnyxDB breaks the app.The
Documents/folder is also where downloaded PDFs/documents land, and the Files-app exposure is how users reach them (added in [Mobile-Expensify#2380728e0a]). Hiding the folder would make previously downloaded files unreachable (discussion), so this PR keeps the folder visible and instead moves every internal file out ofDocuments/, leaving it to hold only files the user expects to see: their downloads and queued receipt uploads.src/libs/fileDownload/index.ios.ts): non-media downloads keep saving toDocuments/. Temporary files move out of it: video downloads stage inLibrary/Cachesbefore being saved to Photos, and the CSV-export flow writes its share-sheet hand-off file toLibrary/Cachesinstead ofDocuments/Expensify.Library/Application Supportvia areact-native-nitro-sqlitepatch ([patches/react-native-nitro-sqlite+9.6.0.patch]) that also migrates existingOnyxDB*files out ofDocuments/at startup. The DB path is hardcoded toNSDocumentDirectoryin the library's native+load, so this cannot be fixed at the app level; the change should be offered upstream so the patch can eventually be dropped.Library/Caches(re-downloadable, so purgeable is fine).src/libs/ExportOnyxState/index.native.ts) and temporary share files (src/libs/localFileCreate/index.native.ts) →Library/Caches.src/libs/migrations/MoveFilesOutOfDocuments) deletes the old attachment cache (sources re-cache on demand) and removes a stale Onyx state dump if one was left inDocuments/. Queued receipt uploads stay inDocuments/on purpose — they are user content, so they remain visible in the Files app.Companion Mobile-Expensify PR (moves OldDot app data out of
Documents/while leaving user downloads in place):MOBILE-EXPENSIFY: https://github.com/Expensify/Mobile-Expensify/pull/14023
Fixed Issues
$ https://github.com/Expensify/Expensify/issues/643611
PROPOSAL:
Tests
All file-visibility checks below refer to the iOS Files app under On My iPhone → New Expensify.
A. Fresh install (no migration)
OnyxDB*files, noattachmentsfolder, and no other internal files.B. Downloads (must stay user-visible)
Expensifyfolder is left behind in the Files app folder.2024.pdf). Relaunch the app and verify the file is still visible in the Files app folder (the OldDot migration must not sweep numeric-named user downloads).C. Receipt uploads (user content, must stay visible)
Receipts-Upload. Kill and reopen the app, go online, and verify the receipt uploads.D. Attachment cache
attachmentsfolder appears in the Files app folder.E. Internal/debug files
F. Upgrade migration (NewDot)
mainand sign in. Open image attachments in a chat (populates the attachment cache), download a PDF attachment, and export Onyx state (the old build leaves the dump in Documents). Install this build over it and verify:OnyxDB*including-wal/-shmmigrated to Application Support).OnyxDB*files, theattachmentsfolder, or the Onyx state dump.G. Upgrade migration (OldDot / HybridApp)
mainHybridApp build, use Expensify Classic (OldDot): sign in and capture a receipt in Classic. Upgrade to this build, open Classic again, and verify:Application Support/ExpensifyData)./Documents/paths are re-rooted onto the new data directory).*.jsonfiles,gpsLocations.txt,logPacketsQueue,<UUID>.jpgreceipts, or hash-named image caches (e.g.93244983.jpg) — while user downloads remain in place.ExpensifyData).H. Resilience (the original bug)
Android sanity
On Android, download a PDF and an image, open attachments, and scan a receipt. Verify behavior is unchanged (all path changes are iOS-scoped).
Verify that no errors appear in the JS console throughout.
Offline tests
QA Steps
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
ScreenRecording_07-21-2026.15-23-31_1.MP4
iOS: mWeb Safari