Skip to content

Commit 112db8a

Browse files
feat(agent,core): organization-key support; tui: Space reveals from any pane (#47)
Two fixes from daily-driving Vault against a Bitwarden-cloud account whose vault is almost entirely organization/Collection-owned. Organization-key support (the bulk of the change): - Vault decrypted only personal ciphers (and per-cipher keys under the user key); organization items — ~99% of a Collections-heavy vault — were skipped, their fields being encrypted under an org key Vault didn't hold. At unlock the agent now recovers the account RSA private key from `profile.privateKey`, RSA-OAEP-SHA1-unwraps each `profile.organizations[].key` into that org's symmetric key, and routes every cipher's decryption by `organization_id` (org key vs user key). Works online and from the offline cache. New `vault_core::org_key` module + `rsa` dependency (`ring` has no RSA decryption); justified RUSTSEC-2023-0071 ignore in deny.toml / CI. Items whose org key can't be unwrapped are still skipped; editing org items is refused for now (the write path would re-encrypt under the wrong key). TUI Space-to-reveal: - `Space` revealed a login's password from the item list but did nothing in the detail pane (logins have no per-field rows) or the folder pane. Target resolution moved into a testable `App::reveal_target`, so `Space` reveals from all three panes; cards/identities still reveal the cursor-selected masked field in the detail pane. Tests: RSA round-trip, org-cipher decrypt + edit-refusal, reveal_target across panes/types. Full `just ci` green; verified live (2274 items decrypt). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5a6a8e7 commit 112db8a

15 files changed

Lines changed: 635 additions & 32 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,11 @@ jobs:
111111
# ratatui); revisit when ratatui's tree updates.
112112
# RUSTSEC-2024-0436 — paste (unmaintained, build-time proc-macro)
113113
# RUSTSEC-2026-0002 — lru 0.12.5 (unsound IterMut; transitive via ratatui)
114-
ignore: RUSTSEC-2024-0436,RUSTSEC-2026-0002
114+
# RUSTSEC-2023-0071 — rsa (Marvin timing sidechannel, no upstream fix).
115+
# rsa unwraps organization keys (RSA-OAEP) once, locally, at unlock;
116+
# the attack needs a network-observable decryption oracle, which Vault
117+
# never exposes. Revisit if rsa ships a constant-time fix.
118+
ignore: RUSTSEC-2024-0436,RUSTSEC-2026-0002,RUSTSEC-2023-0071
115119

116120
deny:
117121
name: cargo-deny

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ range may break in any release.
3333

3434
### Fixed
3535

36+
- **TUI `Space` reveals the password from every pane.** `Space` revealed a
37+
login's password from the item list, but did nothing while the **detail** pane
38+
was focused (logins have no per-field detail rows, so there was nothing to
39+
reveal) and was inert in the **folder** pane. Reveal target resolution now
40+
lives in a testable `App::reveal_target`: the detail pane reveals the
41+
cursor-selected masked field for cards/identities and falls back to the item's
42+
primary secret for logins/notes, and the list and folder panes both reveal the
43+
selected item's primary secret — so `Space` works from all three panes.
44+
3645
- **Login against current Bitwarden / Vaultwarden servers.** Once the master
3746
password was accepted, the server rejected the token request with
3847
`400 version_header_missing`*"No client version header found, required to
@@ -67,6 +76,19 @@ range may break in any release.
6776

6877
### Added
6978

79+
- **Organization / Collection items now decrypt (org-key support).** Vault
80+
previously skipped every organization-owned cipher — the bulk of a vault that
81+
uses Collections — because it held no key for them. At unlock the agent now
82+
unwraps each organization's symmetric key from `/sync`
83+
(`profile.organizations[].key`, an RSA-OAEP-SHA1 envelope opened with the
84+
account key recovered from `profile.privateKey`) and routes each cipher's
85+
decryption by `organization_id` (org key vs user key). Works online and from
86+
the offline cache. New dependency `rsa` (RSA decryption — `ring` has none);
87+
see the justified `RUSTSEC-2023-0071` ignore in `deny.toml` / CI. Items whose
88+
org key can't be unwrapped are still skipped, and **editing** org items is
89+
refused for now (the write path would re-encrypt under the wrong key) —
90+
reads/copy work.
91+
7092
- **`vault sync` progress spinner.** On a TTY, `vault sync` now animates a
7193
spinner while the agent pulls and decrypts `/sync`, then prints `✓ synced N
7294
items`. Suppressed under `--json` and when stderr is not a terminal, so

Cargo.lock

Lines changed: 156 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ cipher = { version = "0.4", features = ["block-padding"] }
6363
hmac = "0.12"
6464
sha1 = "0.10"
6565
sha2 = "0.10"
66+
# RSA-2048-OAEP-SHA1 — unwraps organization keys (Bitwarden type-4 EncStrings).
67+
# `ring` has no RSA decryption, so this is the one non-ring asymmetric path.
68+
# Carries RUSTSEC-2023-0071 (Marvin timing sidechannel, no upstream fix); see the
69+
# justified `cargo audit` ignore in `justfile` / CI. Not reachable as a network
70+
# oracle here — org keys are unwrapped once, locally, at unlock.
71+
rsa = "0.9"
6672
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac", "sha2"] }
6773
argon2 = { version = "0.5", default-features = false, features = ["alloc"] }
6874
hkdf = "0.12"

0 commit comments

Comments
 (0)