Found during adversarial review of #8237 adoption. Upstream code, adopted as-is.
Problem
apps/mobile/src/features/sharing/IncomingShareProvider.tsx:157-163:
consthasGenericFilePayload=payloads.some((p)=>["file","audio","video"].includes(p.shareType));constresolvedPayloads=!hasGenericFilePayload&&payloads.some((p)=>p.shareType==="image")
? awaitresolvedPayloadsForFiles()
: [];
The image branch's only size check before reading bytes is gated on that resolution (incoming-share-model.ts:396-403). With resolved === undefined the check is skipped and control reaches :409await input.fileReader.readBase64(uri) -> new File(uri).base64(), uncapped.
Trigger
Newly reachable because the same changeset widened registration: app.config.ts now uses singleShareMimeTypes: ["*/*"] / multipleShareMimeTypes: ["*/*"] on Android and supportsFileWithMaxCount: 8 on iOS.
Share one PDF + one large photo together from a file manager. Also fires on any image share where getResolvedSharedPayloadsAsync() throws, since the catch at :63-70 also returns [].
Consequence
A 300 MB image is materialised as a ~400 MB base64 string before the 10 MB check that would have rejected it.
Amplifier
incoming-share-inbox.ts:129 calls clearNativePayloads() only after buildDraft and writeDraft succeed, and iOS payloads persist in the App Group UserDefaults. If OOM kills the process rather than throwing a catchable error, refresh() re-ingests the identical payload on next launch — a launch loop with no in-app escape.
Related
The image branch trusts resolved.contentSize, which on Android is the sender's OpenableColumns.SIZE, not a measurement. The file branch was explicitly hardened against exactly this (incoming-share-model.ts:339-358 measures the persisted copy, test at incoming-share-model.test.ts:245). The image branch was not.
Sketch
Measure before reading, or gate the base64 read on a stat regardless of which branch resolved. The !hasGenericFilePayload && clause has no comment or test explaining its rationale — worth recovering that before changing it.
Found during adversarial review of #8237 adoption. Upstream code, adopted as-is.
Problem
apps/mobile/src/features/sharing/IncomingShareProvider.tsx:157-163:The image branch's only size check before reading bytes is gated on that resolution (
incoming-share-model.ts:396-403). Withresolved === undefinedthe check is skipped and control reaches:409await input.fileReader.readBase64(uri)->new File(uri).base64(), uncapped.Trigger
Newly reachable because the same changeset widened registration:
app.config.tsnow usessingleShareMimeTypes: ["*/*"]/multipleShareMimeTypes: ["*/*"]on Android andsupportsFileWithMaxCount: 8on iOS.Share one PDF + one large photo together from a file manager. Also fires on any image share where
getResolvedSharedPayloadsAsync()throws, since the catch at:63-70also returns[].Consequence
A 300 MB image is materialised as a ~400 MB base64 string before the 10 MB check that would have rejected it.
Amplifier
incoming-share-inbox.ts:129callsclearNativePayloads()only afterbuildDraftandwriteDraftsucceed, and iOS payloads persist in the App GroupUserDefaults. If OOM kills the process rather than throwing a catchable error,refresh()re-ingests the identical payload on next launch — a launch loop with no in-app escape.Related
The image branch trusts
resolved.contentSize, which on Android is the sender'sOpenableColumns.SIZE, not a measurement. The file branch was explicitly hardened against exactly this (incoming-share-model.ts:339-358measures the persisted copy, test atincoming-share-model.test.ts:245). The image branch was not.Sketch
Measure before reading, or gate the base64 read on a stat regardless of which branch resolved. The
!hasGenericFilePayload &&clause has no comment or test explaining its rationale — worth recovering that before changing it.