Skip to content

feat(vault): route PAM gated ciphers through the SDK (partial-cipher pivot) - #22171

Draft
Hinton wants to merge 1 commit into
mainfrom
pam/vault-gating-seams
Draft

feat(vault): route PAM gated ciphers through the SDK (partial-cipher pivot)#22171
Hinton wants to merge 1 commit into
mainfrom
pam/vault-gating-seams

Conversation

@Hinton

@Hinton Hinton commented Jul 31, 2026

Copy link
Copy Markdown
Member

🎟️ Tracking

Strategy pivot for PAM cipher gating. Supersedes #22168, #22169, #22170 (now closed). Depends on bitwarden/sdk-internal#1359.

⚠️ Draft / blocked on the SDK: consumes types (Cipher.partialData, the partial view flag, and the RestrictedCipherData field allowlist) from sdk-internal#1359. The @bitwarden/sdk-internal version bump lands once that publishes; until then CI typecheck fails on three lines (cipher.ts toSdkCipher/fromSdkCipher, cipher.view.ts fromSdkCipherView). Reviewable now against a local WASM link. The branch is already squashed to a single commit and rebased onto latest main; the SDK version bump is the only remaining pre-merge step.

📔 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 partialData envelope (encrypted name + login URIs only) in place of the full payload, and the SDK decrypts it into a view marked partial.

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 (RestrictedCipherData in #1359), not in this repo.

What changed

Model plumbing. CipherResponse.partialData (optional) flows verbatim → CipherData → domain Cipher.partialData, round-tripping losslessly through toSdkCipher/fromSdkCipher. The SDK decrypts gated rows like any other cipher (no client-side partition, no SdkRecordMapper.shouldInclude filter, no deprecated Cipher.decrypt) and stamps partial: boolean on the decrypted CipherView/CipherListView.

Web-vault-only, excluded-by-default streams. CipherService splits its decrypted streams so partials are excluded by default and only the web list opts in:

  • getAllDecrypted() excludes partials at the root (a private getAllDecryptedIncludingPartials() 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.
  • The web vault list consumes the new opt-in cipherListViewsWithPartials$.
  • CipherViewLikeUtils.isPartial centralizes 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_BANNER seam (handed the CipherView directly). A host-provided "Controlled access" badge column (VAULT_ROW_LEASE_BADGE seam) appears only when the Pam feature 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 the partialData marker.

Filter tree. The web filter (type filter + folder tree, via a web-only VaultFilterService subclass) 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 emits partialData.

🚨 Breaking Changes

None — inert until a PAM provider binds the badge seam and the server emits partialData. With PAM off, no cipher is partial, so every added filter is a no-op and behavior is unchanged.

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.68966% with 17 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (pam/cipher-partial-decrypt@ad7806d). Learn more about missing BASE report.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...c/vault-item-dialog/vault-item-dialog.component.ts 55.26% 15 Missing and 2 partials ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Hinton Hinton changed the title feat(vault): add the seams for rendering and revealing a gated cipher feat(vault): route PAM gated ciphers through the SDK (partial-cipher pivot) Aug 6, 2026
@Hinton
Hinton changed the base branch from pam/cipher-partial-decrypt to main August 6, 2026 17:00
@Hinton
Hinton force-pushed the pam/vault-gating-seams branch from 6771120 to a0c1558 Compare August 6, 2026 18:32
Comment thread apps/web/src/app/vault/components/vault-items/vault-items.component.ts Outdated
Comment thread apps/web/src/app/vault/individual-vault/cipher-open-gate.ts Outdated
Comment thread libs/common/src/platform/services/sdk/client-managed-state.ts Outdated
Comment thread libs/common/src/vault/models/data/cipher.data.ts Outdated
Comment thread libs/common/src/vault/models/response/cipher.response.ts Outdated
Comment thread libs/vault/src/tokens/gated-cipher-reloader.token.ts Outdated
Comment thread libs/vault/src/cipher-view/cipher-view.component.html Outdated
Comment thread libs/common/src/vault/services/cipher.service.ts Outdated
Comment thread libs/common/src/vault/services/cipher.service.ts Outdated
Comment thread libs/common/src/vault/services/cipher.service.ts
Comment thread libs/common/src/vault/models/domain/cipher.ts
@Hinton
Hinton force-pushed the pam/vault-gating-seams branch from fc9198d to 2dfba20 Compare August 14, 2026 09:26
@Hinton Hinton added the t:feature Change Type - Feature Development label Aug 14, 2026
…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
Hinton force-pushed the pam/vault-gating-seams branch from 2dfba20 to 76b9e53 Compare August 14, 2026 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant