fix(mobile): Android image metadata stripping fixes#2188
Conversation
Co-authored-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co>
|
@codex review |
|
@builderbot review |
|
To use Codex here, create a Codex account and connect to github. |
Co-authored-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co>
brow
left a comment
There was a problem hiding this comment.
🤖 Re-reviewed exact head 77ea4bb. The unsigned SOI comparison and malformed double-SOI regression now enforce the relay contract correctly. I found no blocking issues. One non-blocking fault-visibility concern is noted inline.
Co-authored-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co>
brow
left a comment
There was a problem hiding this comment.
🤖 Independent review at exact head 7d8bd73 (second reviewer in the dual-review loop; delta from 77ea4bb is the narrowed upload-boundary catch).
Verified:
- Narrowing delta is correct and complete:
runCatching {}.getOrNull()→try/catch (IllegalArgumentException). The sanitizer's fail-closed rejections (require) map to the normal upload-failure path, whileerror()'sIllegalStateException(unreachable format invariant) andOutOfMemoryErrornow propagate instead of masquerading as upload failures. This resolves the fault-visibility concern without widening behavior: a malformedBitmap.compressoutput still never uploads unsanitized. - PNG scrub: strict allowlist; drops
eXIf/iCCP/tEXt/iTXt/zTXt/tIME/pHYswhile keeping color-neutral and APNG chunks; length fields validated via 64-bit arithmetic (no overflow on hostile lengths); truncated chunks and missingIENDfail closed; trailing bytes afterIENDdropped (tested with appended payload). - JPEG scrub: keeps only zero-thumbnail-consistent JFIF APP0 and exact 12-byte Adobe APP14; drops APP1–APP13/APP15/COM (EXIF, XMP, ICC); entropy-coded data with byte stuffing and restart markers handled; nested SOI rejected using the unsigned comparison (77ea4bb fix) with the exact malformed double-SOI regression present; missing EOI fails closed; trailing bytes dropped.
- sRGB-before-encode:
decodeSrgbBitmaptargets sRGB at decode (ImageDecoder ≥P with software allocator,inPreferredColorSpaceon 26–27) and re-draws to an ARGB_8888 sRGB bitmap otherwise, so droppingiCCP/APP2 cannot ship mis-tagged Display-P3 pixels; pre-O passes through where no color management exists. Instrumented test pins the P3→sRGB conversion within tolerance for both formats. - Fixture integrity: all Bitmap.compress fixtures are byte-identical across
crates/buzz-media, JVM-test, and androidTest resources (cmpclean); canonical sRGB PNG is a scrub fixed point (sanitized == input); regeneration procedure documented against API 36. - Relay contract closes the loop:
cargo test -p buzz-media android_passes locally at this head — all four sanitized fixtures accepted, all three metadata-bearing raw encoder outputs rejected withMetadataForbidden. These Rust tests run in the CI Unit Tests job (rust path filter coverscrates/**). - CI: fully green at 7d8bd73 (30 success / 2 skipped publish jobs), including Mobile and Unit Tests.
Two non-blocking inline notes below (CI wiring for the new Kotlin JVM tests; pre-existing broad catch on the decode fallback). Verdict: approve.
| } | ||
| } | ||
|
|
||
| dependencies { |
There was a problem hiding this comment.
🤖 Non-blocking: the new JVM sanitizer tests (app:testDebugUnitTest) are not executed by the CI mobile job, which runs flutter test and flutter build apk only (assembleDebug does not run unit tests). The relay-side contract tests in crates/buzz-media do run in CI and pin the actual byte contract, so this is coverage redundancy rather than a gap — but wiring testDebugUnitTest into the mobile CI job in a follow-up would keep the structural scrubber tests from ever going stale unnoticed.
| @RequiresApi(Build.VERSION_CODES.O) | ||
| private fun decodeColorManagedBitmap(bytes: ByteArray): Bitmap? { | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | ||
| runCatching { |
There was a problem hiding this comment.
🤖 Non-blocking, pre-existing pattern: this decode-path runCatching still catches every Throwable (including OOM), in contrast to the now-narrowed encode path. It guards a genuine documented fallback (ImageDecoder → BitmapFactory handles real decode failures), so converting decode faults into the BitmapFactory attempt is defensible; flagging only so the asymmetry with the encode path is a recorded choice rather than an accident.
…obile-releasing * origin/main: chore(release): release Buzz Mobile version 0.4.9 (#2200) Simplify first-community onboarding choices (UI only) (#2194) fix(mobile): sanitize Android image uploads (#2188) Co-authored-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co> # Conflicts: # mobile/pubspec.yaml
…chive * origin/main: (25 commits) feat(cli): filter archived instances from --template roster resolution (#2207) chore(release): release Buzz Desktop version 0.4.21 (#2209) fix(desktop): clarify data deletion action (#2208) feat: brand authentication complete page (#2192) fix(buzz-acp,buzz-agent): surface stall duration and fate (#2204) fix(desktop): resolve activity feed showing channel UUID instead of message content (#2201) feat(cli): add agents archive/unarchive/archived subcommands (#2173) chore(acp): strip stale finding-number references from comments (#2202) chore(release): release Buzz Mobile version 0.4.9 (#2200) Simplify first-community onboarding choices (UI only) (#2194) fix(mobile): sanitize Android image uploads (#2188) fix(cli): paginate channel directory queries (#2181) Fix persisted agent defaults display (#2182) [codex] Fix missing release tag detection (#2191) feat(web): authenticate gated community browsing (#2190) fix(timeout): unified turn-timeout fix — cap inheritance, steer renewal, activity-aware requeue, LLM stall surfacing (#2175) fix(desktop): scope draft store per workspace relay (#2179) chore(release): release Buzz Mobile version 0.4.8 (#2187) [codex] create release tags with dedicated App (#2186) fix(desktop): avoid forwarding click event as channel callback (#2174) ...
…chive * origin/main: (25 commits) feat(cli): filter archived instances from --template roster resolution (#2207) chore(release): release Buzz Desktop version 0.4.21 (#2209) fix(desktop): clarify data deletion action (#2208) feat: brand authentication complete page (#2192) fix(buzz-acp,buzz-agent): surface stall duration and fate (#2204) fix(desktop): resolve activity feed showing channel UUID instead of message content (#2201) feat(cli): add agents archive/unarchive/archived subcommands (#2173) chore(acp): strip stale finding-number references from comments (#2202) chore(release): release Buzz Mobile version 0.4.9 (#2200) Simplify first-community onboarding choices (UI only) (#2194) fix(mobile): sanitize Android image uploads (#2188) fix(cli): paginate channel directory queries (#2181) Fix persisted agent defaults display (#2182) [codex] Fix missing release tag detection (#2191) feat(web): authenticate gated community browsing (#2190) fix(timeout): unified turn-timeout fix — cap inheritance, steer renewal, activity-aware requeue, LLM stall surfacing (#2175) fix(desktop): scope draft store per workspace relay (#2179) chore(release): release Buzz Mobile version 0.4.8 (#2187) [codex] create release tags with dedicated App (#2186) fix(desktop): avoid forwarding click event as channel callback (#2174) ... Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…chive * origin/main: (25 commits) feat(cli): filter archived instances from --template roster resolution (#2207) chore(release): release Buzz Desktop version 0.4.21 (#2209) fix(desktop): clarify data deletion action (#2208) feat: brand authentication complete page (#2192) fix(buzz-acp,buzz-agent): surface stall duration and fate (#2204) fix(desktop): resolve activity feed showing channel UUID instead of message content (#2201) feat(cli): add agents archive/unarchive/archived subcommands (#2173) chore(acp): strip stale finding-number references from comments (#2202) chore(release): release Buzz Mobile version 0.4.9 (#2200) Simplify first-community onboarding choices (UI only) (#2194) fix(mobile): sanitize Android image uploads (#2188) fix(cli): paginate channel directory queries (#2181) Fix persisted agent defaults display (#2182) [codex] Fix missing release tag detection (#2191) feat(web): authenticate gated community browsing (#2190) fix(timeout): unified turn-timeout fix — cap inheritance, steer renewal, activity-aware requeue, LLM stall surfacing (#2175) fix(desktop): scope draft store per workspace relay (#2179) chore(release): release Buzz Mobile version 0.4.8 (#2187) [codex] create release tags with dedicated App (#2186) fix(desktop): avoid forwarding click event as channel callback (#2174) ... Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
* origin/main: fix(desktop): keep project branches after reload (#2214) fix(git): make project branch workflows reliable (#2213) feat(desktop): manage project branches (#2212) fix(acp): honor relay rate limits and pace resubscribes on bad links (#2199) chore(lefthook): add CI-style path filtering to hooks (#2211) feat(cli): manage repository protection rules (#2193) feat(cli): filter archived instances from --template roster resolution (#2207) chore(release): release Buzz Desktop version 0.4.21 (#2209) fix(desktop): clarify data deletion action (#2208) feat: brand authentication complete page (#2192) fix(buzz-acp,buzz-agent): surface stall duration and fate (#2204) fix(desktop): resolve activity feed showing channel UUID instead of message content (#2201) feat(cli): add agents archive/unarchive/archived subcommands (#2173) chore(acp): strip stale finding-number references from comments (#2202) chore(release): release Buzz Mobile version 0.4.9 (#2200) Simplify first-community onboarding choices (UI only) (#2194) fix(mobile): sanitize Android image uploads (#2188) fix(cli): paginate channel directory queries (#2181) Semantic resolutions in crates/buzz-acp/src/lib.rs: - kept lazy_pool startup + pool_ready select guard from this branch, adopted main's comment cleanup (#2202) - dropped this branch's duplicated steer_renewal_tests module: #2204 moved those tests to queue.rs on main Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
What changed?
Android image uploads now decode into sRGB before encoding, preserve PNG transparency, and structurally remove PNG and JPEG metadata that the relay rejects. Android 7.x retains a compatible decode path without raising the minimum supported API.
Real API 36
Bitmap.compressfixtures and relay contract tests cover sRGB and Display P3 PNG/JPEG output.Why?
Android image uploads can fail with HTTP 422 when platform encoding emits ICC profiles or other non-canonical metadata. The client must meet the relay contract without weakening the relay's metadata policy.
How is it tested?
Added tests:
Validated on API 24 and API 36, including Display P3 conversion and transparent PNG pixel preservation. Full pre-push mobile, Rust, desktop, and Tauri suites passed. Android lint has no new API findings and remains blocked by two existing unrelated errors.
Related: BOT-1389
Screenshots are not practical for this non-visual media-encoding change. Coverage verifies output color space, pixels, alpha, metadata structure, and relay acceptance instead.
🤖 This PR was authored with an agent.