feat(vault): route PAM gated ciphers through the SDK (partial-cipher pivot) - #22171
Draft
Hinton wants to merge 1 commit into
Draft
feat(vault): route PAM gated ciphers through the SDK (partial-cipher pivot)#22171Hinton wants to merge 1 commit into
Hinton wants to merge 1 commit into
Conversation
This was referenced Jul 31, 2026
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## pam/cipher-partial-decrypt #22171 +/- ##
=============================================================
Coverage ? 52.74%
=============================================================
Files ? 4207
Lines ? 133252
Branches ? 20977
=============================================================
Hits ? 70284
Misses ? 57829
Partials ? 5139 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Hinton
force-pushed
the
pam/vault-gating-seams
branch
from
August 6, 2026 18:32
6771120 to
a0c1558
Compare
Hinton
commented
Aug 13, 2026
Hinton
commented
Aug 14, 2026
Hinton
force-pushed
the
pam/vault-gating-seams
branch
from
August 14, 2026 09:26
fc9198d to
2dfba20
Compare
…pivot)
Route PAM-gated ("partial") ciphers through the SDK and surface them only in
the web vault, where they render read-only with a "Controlled access" badge. A
partial cipher ships a reduced `partialData` envelope in place of its full
payload; the SDK decrypts it into a view marked `partial`. Everywhere outside
the web vault list, partials are excluded — they never reach autofill, export,
reports, Fido2, key rotation, or the CLI, and are never modifiable.
Model plumbing: `CipherResponse.partialData` flows verbatim through `CipherData`
to domain `Cipher.partialData`, round-tripping losslessly through
`toSdkCipher`/`fromSdkCipher`.
Excluded-by-default streams: `getAllDecrypted()` and `cipherViews$` /
`cipherListViews$` exclude partials, derived from private partials-inclusive
sources so decryption stays single-pass. The web list consumes the opt-in
`cipherListViewsWithPartials$`. `CipherViewLikeUtils.isPartial` centralizes the
flag read.
Read-only rendering + badge seam: a gated cipher opens read-only in the
vault-item dialog and cipher-view; a host-provided "Controlled access" badge
column (`VAULT_ROW_LEASE_BADGE`) appears only when the `Pam` flag is on, a
`usePam` org is in view, and a host provides the badge.
Non-modifiable in web: gated rows are non-selectable and expose no modify menu
actions; both bulk-action paths filter them defensively; encrypted export
excludes them. The web filter tree includes partials so a type/folder present
only as gated rows still surfaces.
Inert until a PAM provider binds the badge seam and the server emits
`partialData`; with PAM off, every added filter is a no-op.
Hinton
force-pushed
the
pam/vault-gating-seams
branch
from
August 14, 2026 09:29
2dfba20 to
76b9e53
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎟️ Tracking
Strategy pivot for PAM cipher gating. Supersedes #22168, #22169, #22170 (now closed). Depends on bitwarden/sdk-internal#1359.
📔 Objective
Route PAM-gated ("partial") ciphers through the SDK and surface them only in the web vault, where they render read-only with a "Controlled access" badge. A partial cipher is a row whose sensitive fields the server suppressed (the caller lacks a privileged-access lease); it ships a reduced
partialDataenvelope (encrypted name + login URIs only) in place of the full payload, and the SDK decrypts it into a view markedpartial.Everywhere outside the web vault list, partials are excluded — they must never reach autofill, export, reports, Fido2, key rotation, or the CLI, and must never be modifiable (editing/re-encrypting a gated row would clobber the server-suppressed fields with blanks).
The security-critical field allowlist (a gated view exposes only name + login URIs, never password/TOTP) lives in the SDK (
RestrictedCipherDatain #1359), not in this repo.What changed
Model plumbing.
CipherResponse.partialData(optional) flows verbatim →CipherData→ domainCipher.partialData, round-tripping losslessly throughtoSdkCipher/fromSdkCipher. The SDK decrypts gated rows like any other cipher (no client-side partition, noSdkRecordMapper.shouldIncludefilter, no deprecatedCipher.decrypt) and stampspartial: booleanon the decryptedCipherView/CipherListView.Web-vault-only, excluded-by-default streams.
CipherServicesplits its decrypted streams so partials are excluded by default and only the web list opts in:getAllDecrypted()excludes partials at the root (a privategetAllDecryptedIncludingPartials()retains them solely as the shared decrypt source), covering every imperative consumer — export, reports, Fido2, ssh-agent, autofill card/identity suggestions, key rotation, CLI.cipherViews$/cipherListViews$exclude partials, derived from private partials-inclusive sources so decryption stays single-pass.cipherListViewsWithPartials$.CipherViewLikeUtils.isPartialcentralizes the flag read.Read-only rendering + badge seams. A gated cipher opens read-only in the vault-item dialog (Edit hidden, form-mode redirected to view) and cipher-view, where a host can render an optional banner through the
CIPHER_VIEW_BANNERseam (handed theCipherViewdirectly). A host-provided "Controlled access" badge column (VAULT_ROW_LEASE_BADGEseam) appears only when thePamfeature flag is on, a PAM-enabled org (Organization.usePam) is in view, and a host provides the badge — otherwise the table is unchanged.Non-modifiable in web. Gated rows are non-selectable (checkbox disabled + excluded from
editableItems/select-all) and expose no modify menu actions; both bulk-action paths (classic emitters +VaultBatchBarService) filter them defensively; encrypted export excludes them via thepartialDatamarker.Filter tree. The web filter (type filter + folder tree, via a web-only
VaultFilterServicesubclass) includes partials so a type/folder present only as gated rows still surfaces; other clients keep the excluded stream. Collections are unaffected.Verification
test:types,lint, and prettier are clean on the affected projects (save the three SDK-blocked lines above). Touched + model specs pass, including new coverage for the stream split/exclusion,isPartial, the read-only rendering, the badge column gate, non-selectability, bulk-action + export exclusion, and key-rotation exclusion. E2E stays a no-op until a real PAM provider binds the badge seam and the server emitspartialData.🚨 Breaking Changes
None — inert until a PAM provider binds the badge seam and the server emits
partialData. With PAM off, no cipher ispartial, so every added filter is a no-op and behavior is unchanged.