Skip to content

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

Open
yuuouu wants to merge 2 commits intobitwarden:mainfrom
yuuouu:pull
Open

[PM-34032] feat: secure inline image attachment previews with privacy mask#6711
yuuouu wants to merge 2 commits intobitwarden:mainfrom
yuuouu:pull

Conversation

@yuuouu
Copy link
Copy Markdown

@yuuouu yuuouu commented Mar 24, 2026

📝 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.
10_18_80_125_6666_20260324_132035_600.mp4

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.

- 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.
@yuuouu yuuouu requested review from a team and david-livefront as code owners March 24, 2026 06:08
@yuuouu yuuouu closed this Mar 24, 2026
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 24, 2026

CLA assistant check
All committers have signed the CLA.

@yuuouu yuuouu deleted the pull branch March 24, 2026 06:09
@yuuouu yuuouu restored the pull branch March 24, 2026 06:10
@yuuouu yuuouu reopened this Mar 24, 2026
@bitwarden-bot
Copy link
Copy Markdown

Thank you for your contribution! We've added this to our internal tracking system for review.
ID: PM-34032
Link: https://bitwarden.atlassian.net/browse/PM-34032

Details on our contribution process can be found here: https://contributing.bitwarden.com/contributing/pull-requests/community-pr-process.

@bitwarden-bot bitwarden-bot changed the title feat: secure inline image attachment previews with privacy mask [PM-34032] feat: secure inline image attachment previews with privacy mask Mar 24, 2026
@yuuouu
Copy link
Copy Markdown
Author

yuuouu commented Mar 31, 2026

Hi @david-livefront , Just checking in on this PR. I understand that since this involves core privacy mechanisms (memory-only caching & lifecycle-bound purging constraints), it might need deeper security and QA reviews. Please let me know if there’s anything I can clarify or any testing scenarios I can assist with whenever it makes it to your sprint backlog!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants