feat(core,api,agent): edit organization-owned items - #55
Conversation
`vault edit` refused organization ciphers ("editing organization items is
not supported yet") — a blocker on a vault that is ~99% org-owned. Enable it,
and fix the wire-body gap that made it (and personal card/identity edits)
impossible.
- vault-api: CipherRequest (the POST/PUT body) only sent
type/folderId/name/notes/login/secureNote, silently dropping card,
identity, and custom fields — so personal card/identity edits never reached
the server. It now also carries organizationId and key, which an org PUT
must echo back or it strips the item's org membership and per-cipher key.
- vault-core: add Cipher::field_keys — resolves the (enc, mac) a cipher's
fields are encrypted under, unwrapping the per-cipher key under the base
(org/user) key or falling back to it. Symmetric to Cipher::decrypt on the
read path, so a re-encrypted field round-trips.
- vault-agent: edit_cipher resolves the base key via the new
Vault::edit_base_keys (org key when held, else user key) and re-encrypts
changed fields under the resolved per-cipher key. An org item whose key this
session does not hold is refused up front rather than corrupted.
Adding a new item directly into an org/collection stays out of scope (needs
collection selection); vault add still creates personal items.
Tests: field_keys round-trip + wrong-base + keyless (vault-core);
CipherRequest carries org/key/card/identity/fields and omits them for
personal items (vault-api); org-edit gating — held key reaches the network,
unheld key refused (vault-agent). No live writes; the end-to-end write
against a real org is a maintainer step (docs/m2-vaultwarden.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 23d16c7200
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #[serde(rename = "organizationId", skip_serializing_if = "Option::is_none")] | ||
| organization_id: Option<&'a str>, |
There was a problem hiding this comment.
Preserve collection IDs when updating organization ciphers
When the organization item belongs to one or more collections, the /sync cipher's collectionIds are ignored by vault_core::Cipher, and this newly enabled organization PUT consequently cannot echo them in CipherRequest. Bitwarden uses those IDs to retain collection membership, so such edits can be rejected for lacking an accessible collection or disassociate the item from its existing collections; model and round-trip collectionIds before allowing organization edits.
Useful? React with 👍 / 👎.
| #[serde(skip_serializing_if = "Option::is_none")] | ||
| fields: Option<&'a [vault_core::cipher::CustomField]>, |
There was a problem hiding this comment.
Preserve linked IDs when serializing custom fields
When an item contains a type-3 linked custom field, serializing c.fields now emits only the fields represented by vault_core::cipher::CustomField, which has name, value, and type but no Bitwarden linkedId. The update therefore sends an incomplete linked field, causing the server either to reject the edit or lose the field's link target; add linkedId to the sync model and round-trip it in this body.
Useful? React with 👍 / 👎.
What
Enables
vault edit(and TUIe) on organization-owned items — previouslyrefused with "editing organization items is not supported yet." On a vault
that is ~99% org-owned, that refusal made editing effectively unusable.
Along the way it fixes a latent wire-body gap that also affected personal
items.
The wire-body gap (fix)
CipherRequest(thePOST/PUT /api/ciphersbody) only serializedtype/folderId/name/notes/login/secureNote. It silently droppedcard,identity, and customfields— so personal card/identity edits neverreached the server, despite the in-memory cipher being built correctly. The body
now carries
card,identity,fields, and — for org support —organizationIdand
key(aPUTomitting those strips the item's org membership and itsper-cipher key).
Org-aware edit
An org item's fields are encrypted under a per-cipher key that is itself
wrapped under the organization key. The edit path now:
Vault::edit_base_keys— the org key when held,else the user key. An org item whose key this session does not hold is
refused up front (it can't be decrypted, so it can't be safely re-encrypted)
rather than corrupted.
Cipher::field_keys(symmetric toCipher::decrypton the read path) and re-encrypts only changed fields underit, echoing
organizationId/keyback on thePUT.Out of scope: adding a new item directly into an org/collection (needs
collection selection) —
vault addstill creates personal items.Tests (no live writes)
vault-core:field_keysround-trip (re-encrypt under the resolved item key→ decrypts back), keyless fallback, wrong-base-key error.
vault-api:CipherRequestcarriesorganizationId/key/card/identity/fieldswhen present, and omitsorganizationId/keyfor personal items.vault-agent: org-edit gating — an org item whose key we hold reaches thenetwork step (proving crypto passed), one whose key we lack is refused.
Local gates green via
nix develop:cargo fmt --check,clippy --workspace --all-targets --all-features -D warnings,cargo test. No writes to any realvault — the end-to-end write against a live org is a maintainer step
(
docs/m2-vaultwarden.md).🤖 Generated with Claude Code