Skip to content

Conversation

@fosple
Copy link

@fosple fosple commented Oct 26, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-27382

📔 Objective

This PR is the first step of adding Offline Editing capabilities to Bitwarden, which is one of the highest requested features by the community:
https://community.bitwarden.com/t/offline-editing-management-of-writeable-vault-items/107

This work implements the foundation for conflict detection, conflict-safe sync, and item history/restore.

High-level goal

Enable Bitwarden clients to safely sync edits made offline or concurrently on multiple devices without losing data, while keeping full end-to-end encryption and zero-knowledge guarantees.

This PR introduces:

  • A conflict-aware sync model based on Last-Write-Wins (LWW) semantics for the canonical version, plus encrypted history snapshots of every successful server-side edit for safety and auditability.
  • Client-driven conflict resolution that never exposes plaintext to the server.
  • APIs that let clients view and restore historical versions of an item.

This is the enabling infrastructure for Offline Editing. It is intentionally easy to implement for all clients (mobile, desktop, browser).

Clients only need to:

  • Detect sync conflicts (HTTP 409),
  • Store both versions locally,
  • Decide which version to keep or to merge them
  • Optionally surface item history for restore.

It is also backwards compatible: Existing clients that do not support conflict resolution continue to function.

Behavioral summary

  1. Every cipher update now snapshots the previous state into encrypted history.
  2. If two devices modify the same item independently (classic offline edit case), a sync conflict is detected.
  3. The server responds with HTTP 409 (Conflict) and returns the server’s current cipher.
  4. The client then:
    • Preserves both its local edit and the remote/server version on the device.
    • Decides which version should become canonical. E.g. merge fields or just selects one.
    • Uploads that selected version as the new canonical item, using the latest RevisionDate.
  5. The server automatically stores the previous canonical state in history on each successful write. Client versions that never become canonical are not persisted automatically.

This approach:

  • Avoids server-side merge logic (keeps server stateless with respect to plaintext).
  • Keeps sync simple and robust.
  • Works with fully encrypted ciphers, because all merge/selection logic runs locally after decryption on the client.
  • Guarantees that server-accepted revisions are never silently dropped — even in multi-device race conditions — while leaving room for clients to protect rejected drafts locally or by re-uploading them.

📸 Screenshots

No UI changes in this PR. The new history/restore endpoints will allow future clients to present item history and conflict views, but no user-facing UI is added here.

Changes

New internal logic

  • Added encrypted history tracking: created the CipherHistory table/sql, entity, repositories (Dapper + EF), service registrations, and a DbUp migration (with CipherHistory_Create proc, index, FK) so every cipher update persists a snapshot.
  • Updated CipherService to look up the current record, throw a new SyncConflictException when revisions diverge, log history entries before mutations (including attachment paths and admin deletes), and reuse that helper across flows.
  • Implemented conflict-aware API behavior: CiphersController now wraps write endpoints to surface HTTP 409 responses with the server cipher for user and admin scenarios, plus shared helper methods.
  • Registered the new repositories, exposed the history DbSet on DatabaseContext, and introduced the SyncConflictException type.

New API endpoints

  • Added GET /ciphers/{id}/history
  • Added POST /ciphers/{id}/history/{historyId}/restore
  • Created CipherHistoryResponseModel to serialize history snapshots.
  • Implemented ICipherService.RestoreFromHistoryAsync to snapshot the current cipher, apply historical data, log, and push updates; updated interface accordingly (src/Core/Vault/Services/*).

Data access

  • Extended ICipherHistoryRepository with GetManyByCipherIdAsync and custom GetByIdAsync; added SQL-based implementations for both Dapper and EF providers to fetch histories ordered by timestamp (src/Core/Vault/Repositories/ICipherHistoryRepository.cs, src/Infrastructure.*).

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@fosple fosple requested review from a team as code owners October 26, 2025 16:43
@CLAassistant
Copy link

CLAassistant commented Oct 26, 2025

CLA assistant check
All committers have signed the CLA.

@bitwarden-bot
Copy link

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

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 [Offline Editing] Add core infrastructure for offline-safe edits and version history [PM-27382] [Offline Editing] Add core infrastructure for offline-safe edits and version history Oct 26, 2025
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