Skip to content

fix: decrypt against current Bitwarden cloud (headers, 2FA, camelCase, cipher keys)#46

Merged
UnbreakableMJ merged 1 commit into
mainfrom
bitwarden-cloud-compat
Jun 22, 2026
Merged

fix: decrypt against current Bitwarden cloud (headers, 2FA, camelCase, cipher keys)#46
UnbreakableMJ merged 1 commit into
mainfrom
bitwarden-cloud-compat

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

Daily-driving Vault against Bitwarden's hosted service surfaced a chain of incompatibilities with the modern API — each masked the next, so they only became visible one at a time (none were reachable by the wiremock fixtures, which encoded an older API shape).

Fixes

Login (vault-api)

  • Send the Bitwarden client headers (Bitwarden-Client-Name, Bitwarden-Client-Version, Device-Type) on every request — the hosted server rejects a missing Bitwarden-Client-Version with 400 version_header_missing on the post-auth path, so a correct password failed after authenticating.
  • Parse the 2FA challenge tolerantly: hosted Bitwarden sends provider ids as strings (["0","7"]); a strict Vec<u32> failed the whole parse and surfaced a real 2FA challenge as invalid_grant ("bad password").

Sync & decryption (vault-api + vault-core)

  • /sync and every nested cipher are camelCase on hosted Bitwarden and Vaultwarden; the wire structs were PascalCase, so a full vault synced as zero items. API-service structs flipped to camelCase (identity-service structs stay PascalCase, matching that endpoint).
  • Support cipher-key encryption: each item's fields are encrypted under its own key (wrapped under the user key), not the user key directly — decrypting with the user key failed MAC verification.

Resilience (vault-agent)

  • list/get/resolve skip ciphers they can't decrypt (e.g. organization items, whose keys are wrapped under an org key Vault doesn't hold yet) instead of aborting the whole op; a one-line count is logged. The raw server body is logged on a login 400.

UX (vault-cli)

  • vault sync shows a TTY spinner → "synced N items" (quiet under --json / non-TTY).

Verification

Full just ci green (fmt, clippy -D warnings, test). New tests cover the camelCase sync shape, the string-id 2FA body, cipher-key round-trips, and search/list tolerance. Verified live against Bitwarden cloud: login (password + TOTP), sync (2274 items), list, get, reveal/copy. No new dependencies.

Known follow-up

Organization/Collection items (the bulk of a vault that uses Collections) are currently skipped — org-key (RSA) support is the next PR.

🤖 Generated with Claude Code

…, cipher keys)

Daily-driving Vault against Bitwarden's hosted service surfaced a chain of
incompatibilities with the modern API — each masked the next, so they only
became visible one at a time. None were reachable by the wiremock fixtures,
which encoded an older API shape.

vault-api (login / identity):
- Send the Bitwarden client-identification headers (`Bitwarden-Client-Name`,
  `Bitwarden-Client-Version`, `Device-Type`) on every request. The hosted
  server rejects requests without `Bitwarden-Client-Version` (HTTP 400
  `version_header_missing`) on the post-auth path, so a correct password
  failed *after* authenticating.
- Parse the 2FA challenge tolerantly: the hosted server sends provider ids as
  JSON strings (`["0","7"]`), which a strict `Vec<u32>` rejected — failing the
  whole TwoFactorErrorBody parse and surfacing a real 2FA challenge as
  `invalid_grant` ("bad password").

vault-api + vault-core (sync / decryption):
- `/sync` and every nested cipher are camelCase on hosted Bitwarden and
  Vaultwarden; the wire structs were PascalCase, so every field fell to its
  `#[serde(default)]` and a full vault synced as zero items. Flip the
  API-service structs to camelCase (identity-service structs stay PascalCase,
  matching that endpoint).
- Support "cipher key encryption": when a cipher carries its own `key` (the
  default for current vaults), its fields are encrypted under that per-item
  key — wrapped under the user key — not the user key directly. Decrypting
  with the user key failed MAC on every such item.

vault-agent (resilience):
- Skip ciphers that fail to decrypt (e.g. organization items, whose keys are
  wrapped under an org key Vault doesn't hold) in `list`/`get`/resolve instead
  of aborting the whole operation; tally a one-line count to the log. Also log
  the raw server body on a login 400 so an opaque "bad password" can't mask an
  actionable response again.

vault-cli:
- `vault sync` shows a TTY spinner while the agent pulls and decrypts `/sync`,
  then "synced N items"; suppressed under `--json` / non-TTY.

Tests cover the camelCase sync shape, the string-id 2FA body, cipher-key
round-trips, and search/list tolerance. No new dependencies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@UnbreakableMJ UnbreakableMJ merged commit 5a6a8e7 into main Jun 22, 2026
9 checks passed
@UnbreakableMJ UnbreakableMJ deleted the bitwarden-cloud-compat branch June 22, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant