[PR #6711] [PM-34032] feat: secure inline image attachment previews with privacy mask #12951

Open
opened 2026-04-11 03:54:31 -05:00 by GiteaMirror · 0 comments
Owner

Original Pull Request: https://github.com/bitwarden/android/pull/6711

State: open
Merged: No


📝 Summary

  • Introduce inline image thumbnails in VaultItemScreen with a privacy MaskedOverlay.
  • Implement strict burn-after-reading: decrypted temporary files are deleted immediately after being rendered into Glide's memory.
  • Enforce DiskCacheStrategy.NONE across all Glide requests to prevent plaintext image leaks in third-party caches.
  • Scope VaultMediaViewerViewModel to NavGraph for state sharing, but tie temporary file purging strictly to Lifecycle.Event.ON_DESTROY of VaultItemScreen to prevent cross-session state leakage.
  • Refactor MediaViewerScreen (fullscreen) to perform an independent secondary decryption, decoupling completely from thumbnail file paths.
  • Add isAutoLoadAttachmentsEnabled and isAutoUnmaskAllEnabled flags to pave the way for future user-configurable settings.

🎟️ Tracking

PM-29871: bug: Add more accessibility callouts for external links

📔 Objective

This PR significantly enhances the UX of viewing image attachments by introducing inline thumbnails with a privacy mask, while strictly adhering to Bitwarden's zero-knowledge and zero-trace security standards.

Previously, viewing an attachment required navigating to a completely separate screen. This update allows users to preview images directly within the VaultItemScreen, utilizing a highly secure "burn-after-reading" strategy paired with memory-only caching.

Changes Included

  • Inline Privacy Mask (UX): Image attachments now display a MaskedOverlay inline. Tapping the mask decrypts and loads the thumbnail seamlessly.
  • Strict Burn-After-Reading: Decrypted files are deleted from the disk immediately after Glide successfully loads them into the RAM.
  • Zero Disk Caching: Enforced DiskCacheStrategy.NONE on all Glide requests to guarantee plaintext binaries never linger in the Android file system.
  • Lifecycle-Bound Purging: The media_previews cache and shared VaultMediaViewerViewModel states are completely wiped upon Lifecycle.Event.ON_DESTROY of the VaultItemScreen or upon Vault Lock.
  • Independent Fullscreen Decryption: Navigating to the MediaViewerScreen no longer relies on the thumbnail's filePath. Instead, it triggers a secondary, isolated decryption to ensure large files are handled securely and independently.

Future Settings Configuration (Flags)

To accommodate different user privacy preferences and network constraints, I have introduced two configuration flags in VaultMediaViewerViewModel:

  1. isAutoUnmaskAllEnabled (Currently true): When a user taps one masked image, all other images within the same vault item are automatically decrypted and unmasked for a smoother viewing experience.
  2. isAutoLoadAttachmentsEnabled (Currently false): Determines whether attachments should bypass the privacy mask and load automatically upon entering the vault item screen.

Note: Currently, these are hardcoded properties within the ViewModel. They are explicitly designed and reserved to be moved to SettingsRepository in a future PR, allowing users to toggle these behaviors via the App's "Settings -> Privacy" menu (similar to isAutoCopyTotpDisabled).

📸 Video Demonstration

The video below demonstrates the seamless UX and the strict lifecycle security mechanisms:

  1. Enter Page: Attachments are hidden behind a privacy mask.
  2. Click to Unmask: Clicking one mask securely decrypts and loads the thumbnails (isAutoUnmaskAllEnabled triggers the rest).
  3. Lock / Exit & Re-enter: Locking the vault (or exiting the item) completely purges the memory and disk. Re-entering the item shows that the state is cleanly reset, requiring a manual click to unmask again.

https://github.com/user-attachments/assets/e7f850dd-88a0-4d3d-b67e-743ef7462e99

Security Checklist

  • Plaintext files are never permanently written to disk.
  • Glide disk caching is entirely disabled (DiskCacheStrategy.NONE).
  • Decrypted files and memory states are purged on Vault Lock and Screen Destroy.
**Original Pull Request:** https://github.com/bitwarden/android/pull/6711 **State:** open **Merged:** No --- 📝 Summary - Introduce inline image thumbnails in VaultItemScreen with a privacy MaskedOverlay. - Implement strict burn-after-reading: decrypted temporary files are deleted immediately after being rendered into Glide's memory. - Enforce `DiskCacheStrategy.NONE` across all Glide requests to prevent plaintext image leaks in third-party caches. - Scope VaultMediaViewerViewModel to NavGraph for state sharing, but tie temporary file purging strictly to `Lifecycle.Event.ON_DESTROY` of VaultItemScreen to prevent cross-session state leakage. - Refactor MediaViewerScreen (fullscreen) to perform an independent secondary decryption, decoupling completely from thumbnail file paths. - Add `isAutoLoadAttachmentsEnabled` and `isAutoUnmaskAllEnabled` flags to pave the way for future user-configurable settings. ## 🎟️ Tracking [PM-29871: bug: Add more accessibility callouts for external links](https://github.com/bitwarden/android/pull/6708) ## 📔 Objective This PR significantly enhances the UX of viewing image attachments by introducing **inline thumbnails with a privacy mask**, while strictly adhering to Bitwarden's zero-knowledge and zero-trace security standards. Previously, viewing an attachment required navigating to a completely separate screen. This update allows users to preview images directly within the `VaultItemScreen`, utilizing a highly secure "burn-after-reading" strategy paired with memory-only caching. ## Changes Included - **Inline Privacy Mask (UX):** Image attachments now display a `MaskedOverlay` inline. Tapping the mask decrypts and loads the thumbnail seamlessly. - **Strict Burn-After-Reading:** Decrypted files are deleted from the disk *immediately* after Glide successfully loads them into the RAM. - **Zero Disk Caching:** Enforced `DiskCacheStrategy.NONE` on all Glide requests to guarantee plaintext binaries never linger in the Android file system. - **Lifecycle-Bound Purging:** The `media_previews` cache and shared `VaultMediaViewerViewModel` states are completely wiped upon `Lifecycle.Event.ON_DESTROY` of the `VaultItemScreen` or upon Vault Lock. - **Independent Fullscreen Decryption:** Navigating to the `MediaViewerScreen` no longer relies on the thumbnail's `filePath`. Instead, it triggers a secondary, isolated decryption to ensure large files are handled securely and independently. ## Future Settings Configuration (Flags) To accommodate different user privacy preferences and network constraints, I have introduced two configuration flags in `VaultMediaViewerViewModel`: 1. `isAutoUnmaskAllEnabled` (Currently `true`): When a user taps one masked image, all other images within the same vault item are automatically decrypted and unmasked for a smoother viewing experience. 2. `isAutoLoadAttachmentsEnabled` (Currently `false`): Determines whether attachments should bypass the privacy mask and load automatically upon entering the vault item screen. **Note:** Currently, these are hardcoded properties within the ViewModel. They are explicitly designed and reserved to be moved to `SettingsRepository` in a future PR, allowing users to toggle these behaviors via the App's "Settings -> Privacy" menu (similar to `isAutoCopyTotpDisabled`). ## 📸 Video Demonstration The video below demonstrates the seamless UX and the strict lifecycle security mechanisms: 1. **Enter Page:** Attachments are hidden behind a privacy mask. 2. **Click to Unmask:** Clicking one mask securely decrypts and loads the thumbnails (`isAutoUnmaskAllEnabled` triggers the rest). 3. **Lock / Exit & Re-enter:** Locking the vault (or exiting the item) completely purges the memory and disk. Re-entering the item shows that the state is cleanly reset, requiring a manual click to unmask again. https://github.com/user-attachments/assets/e7f850dd-88a0-4d3d-b67e-743ef7462e99 ## Security Checklist - [x] Plaintext files are never permanently written to disk. - [x] Glide disk caching is entirely disabled (`DiskCacheStrategy.NONE`). - [x] Decrypted files and memory states are purged on Vault Lock and Screen Destroy.
GiteaMirror added the pull-request label 2026-04-11 03:54:31 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/android#12951