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

Open
opened 2026-04-23 22:34:23 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/bitwarden/android/pull/6711
Author: @yuuouu
Created: 3/24/2026
Status: 🔄 Open

Base: mainHead: pull


📝 Commits (2)

  • d5868fe feat: secure inline image attachment previews with privacy mask
  • d8845b4 Merge branch 'main' into pull

📊 Changes

17 files changed (+1892 additions, -144 deletions)

View changed files

📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt (+25 -0)
app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/BurnAfterReadingListener.kt (+48 -0)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemCardContent.kt (+88 -28)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemIdentityContent.kt (+88 -28)
app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemImageAttachmentContent.kt (+386 -0)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt (+86 -27)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemNavigation.kt (+22 -2)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt (+112 -4)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSecureNoteContent.kt (+88 -28)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt (+87 -27)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt (+1 -0)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultCommonItemTypeHandlers.kt (+8 -0)
📝 app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt (+4 -0)
app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/MediaPreviewState.kt (+89 -0)
app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/MediaViewerNavigation.kt (+87 -0)
app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/MediaViewerScreen.kt (+273 -0)
app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/VaultMediaViewerViewModel.kt (+400 -0)

📄 Description

📝 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.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/bitwarden/android/pull/6711 **Author:** [@yuuouu](https://github.com/yuuouu) **Created:** 3/24/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `pull` --- ### 📝 Commits (2) - [`d5868fe`](https://github.com/bitwarden/android/commit/d5868fe5443c3be58cacc80c93d9f2f7265f8915) feat: secure inline image attachment previews with privacy mask - [`d8845b4`](https://github.com/bitwarden/android/commit/d8845b47bf3c50c1f48bbc0c8c993d3640736b29) Merge branch 'main' into pull ### 📊 Changes **17 files changed** (+1892 additions, -144 deletions) <details> <summary>View changed files</summary> 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt` (+25 -0) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/BurnAfterReadingListener.kt` (+48 -0) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemCardContent.kt` (+88 -28) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemIdentityContent.kt` (+88 -28) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemImageAttachmentContent.kt` (+386 -0) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt` (+86 -27) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemNavigation.kt` (+22 -2) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt` (+112 -4) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSecureNoteContent.kt` (+88 -28) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt` (+87 -27) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt` (+1 -0) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/handlers/VaultCommonItemTypeHandlers.kt` (+8 -0) 📝 `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt` (+4 -0) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/MediaPreviewState.kt` (+89 -0) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/MediaViewerNavigation.kt` (+87 -0) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/MediaViewerScreen.kt` (+273 -0) ➕ `app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/media/VaultMediaViewerViewModel.kt` (+400 -0) </details> ### 📄 Description 📝 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. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-23 22:34:23 -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#43898