Skip to content

feat: keep a stored provider credential across an unrelated settings save - #1847

Merged
FSM1 merged 1 commit into
mainfrom
feat/1535-keep-provider-credential-across-settings-save
Sep 15, 2026
Merged

FSM1 merged 1 commit into
mainfrom
feat/1535-keep-provider-credential-across-settings-save

Conversation

@FSM1

@FSM1 FSM1 commented Sep 14, 2026

Copy link
Copy Markdown
Owner

The vault settings record round-tripped every field but the provider bearer. The wasm boundary is write-only for that credential, so a read reports only that one is stored, never its value. A save publishes the record whole, which made a blank credential field mean "no bearer" and turned every unrelated settings change into a credential loss. The fail-closed refusal that shipped with #1503 stopped the loss, at the cost of asking the member to retype the one field that is hardest to retype.

Closes #1535.

What changed

The bearer is three-state at every layer. ByoBearer replaces Option<Zeroizing<String>> on ByoIpfsConfig: None stores no credential, Set carries one, and Keep is a save intent that names no bytes. Engine::save_vault_settings resolves a Keep from the provider config the session placement already holds, so the host still never sees the bearer.

A kept bearer is bound to the provider it was stored for. The save must name the same endpoint and the same provider kind, or it is refused. This is the load-bearing part. The keep intent crosses an untrusted realm boundary, so without the binding a host could name an endpoint it controls, ask to keep a credential it cannot read, and have the next placement present the member's bearer to that endpoint. The web form applies the same rule before the member ever sees an error.

Fail-closed, release-active. validate_byo_config is the one gate, and it refuses an unresolved Keep on the seal path and on every request path. The decode path has no spelling for a keep, so the encode path returns Err rather than asserting. Three verdicts reach the host as MalformedInput checks: byo-credential-not-stored, byo-credential-repointed, byo-credential-unresolved.

The wasm boundary refuses the contradiction. ByoIpfsConfig::new takes the keep flag beside the optional bytes and refuses both together, zeroizing the bytes before the refusal returns.

The client and the form. ByoIpfsConfigDescriptor.accessToken becomes ArrayBuffer | 'keep' | null; commandTransfer still lists only a buffer, so the keep intent transfers nothing. settingsSaveVerdict now answers with the keep decision instead of refusing a blank field, and the form copy says the credential is kept rather than asking for it again.

Tests

  • crates/engine/tests/vault_settings.rs: a keeping save republishes the stored bearer and takes the new retention, read back off the sealed record from a second device; a keep with nothing held, a keep across a repointed endpoint, and a keep across a changed kind are each refused with nothing published.
  • crates/engine/src/settings.rs: the seal path refuses an unresolved keep; the resolve takes, binds and refuses as stated; a supplied bearer is left alone.
  • crates/wasm/tests/boundary.rs: a keep intent carrying its own bearer builds no config.
  • packages/client: the codec spells a keep as the flag with no bearer bytes; the verdict keeps, clears, refuses a repointed endpoint and refuses a repointed kind.
  • apps/web: the form keeps a stored credential through an unrelated edit and refuses to keep one on to a repointed provider.

Suites run: cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace, cargo check -p cipherbox-wasm --target wasm32-unknown-unknown, pnpm -r typecheck, pnpm lint, pnpm lint:tracker-refs, the packages/client suite and the apps/web suite.

Review gates

/simplify, /security-review and /crypto-privacy-review were run inline against git diff main...HEAD. The crypto pass is the one this change was filed for: it re-opens the credential-redaction boundary two gates certified on #1503. It found no primitive, KDF edge or wire-format change, confirmed that ByoBearer prints only its variant name, that VaultSettingsSummary still drops the bearer at construction, and that no new path reads a stored bearer into JavaScript. The security pass produced the same-provider binding described above, which the issue body had placed in the form alone.

Body checks / follow-ups filed

Summary by CodeRabbit

  • New Features

    • Vault settings can now preserve an existing external provider credential when editing unrelated settings.
    • Added clearer credential-state handling for keeping, replacing, or clearing provider credentials.
  • Bug Fixes

    • Prevented unrelated settings changes from accidentally removing stored provider credentials.
    • Added safeguards that reject attempts to preserve credentials while switching provider endpoints or types.
    • Improved validation and error reporting for unavailable or unresolved credentials.

Note

Add ByoBearer keep-intent to preserve stored provider credential across unrelated vault settings saves

  • Replaces the optional access-token field on ByoIpfsConfig with a three-state ByoBearer enum (None, Set, Keep), so a settings save can explicitly request retaining the session-held credential instead of re-sending it
  • settingsSaveVerdict in quota.ts now emits a keepStoredCredential flag; it is true only when the provider endpoint and kind are unchanged and the form field is blank — repointing the provider or entering a new credential refuses the keep
  • resolve_kept_bearer in settings.rs resolves a Keep intent against the held provider, requiring matching endpoint and kind and a real stored token; mismatches return dedicated ProviderError variants (unresolved_credential, missing_stored_credential, repointed_credential)
  • The wasm boundary, worker protocol, and web form layers thread a KEEP_STORED_BEARER sentinel through encoding, validation, and buffer scrubbing, keeping keep intents out of transferable-buffer validation and zeroization paths
  • Risk: ByoIpfsConfig.access_token changes from Option<Zeroizing<String>> to ByoBearer; any out-of-tree caller constructing or reading that field directly must migrate to the new enum and its token() accessor

Macroscope summarized 95dcb66.

@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 87671dc0-90d2-40b1-86d1-eed2216f8e2a

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 29037b6e-ffe7-414f-b5cb-c83d6289ee53

📥 Commits

Reviewing files that changed from the base of the PR and between 0e1094a and 95dcb66.

📒 Files selected for processing (24)
  • apps/web/src/components/settings/VaultSettingsForm.test.tsx
  • apps/web/src/components/settings/VaultSettingsForm.tsx
  • apps/web/src/settings/vaultSettings.test.ts
  • apps/web/src/settings/vaultSettings.ts
  • crates/engine/src/content/mod.rs
  • crates/engine/src/content/provider.rs
  • crates/engine/src/facade.rs
  • crates/engine/src/lib.rs
  • crates/engine/src/settings.rs
  • crates/engine/src/sync/drain.rs
  • crates/engine/src/sync/upload_mark.rs
  • crates/engine/tests/vault_settings.rs
  • crates/engine/tests/write_plane.rs
  • crates/wasm/src/lib.rs
  • crates/wasm/tests/boundary.rs
  • packages/client/src/broadcastTransport.test.ts
  • packages/client/src/facade.ts
  • packages/client/src/index.ts
  • packages/client/src/settings/quota.test.ts
  • packages/client/src/settings/quota.ts
  • packages/client/src/worker/commandCodec.test.ts
  • packages/client/src/worker/commandCodec.ts
  • packages/client/src/worker/engineWasm.ts
  • packages/client/src/worker/protocol.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The change adds a three-state provider bearer model. Settings saves can preserve a stored bearer without exposing it to JavaScript. The client validates provider identity, the WASM boundary carries keep intent, and the engine resolves or rejects that intent.

Changes

Stored bearer preservation

Layer / File(s) Summary
Save policy and UI intent
packages/client/src/settings/quota.ts, packages/client/src/worker/protocol.ts, apps/web/src/components/settings/*, apps/web/src/settings/vaultSettings.ts
The save verdict now reports whether to keep a stored credential. The form sends KEEP_STORED_BEARER for matching providers and refuses repointed providers.
Client transport and WASM bearer intent
packages/client/src/facade.ts, packages/client/src/worker/*, crates/wasm/src/lib.rs
The client converts the keep sentinel into a keep flag. The WASM constructor creates ByoBearer::Keep and rejects a bearer combined with keep intent.
Engine bearer model and resolution
crates/engine/src/content/*, crates/engine/src/settings.rs, crates/engine/src/facade.rs
ByoBearer adds None, Set, and Keep states. The engine resolves Keep from the held provider only when endpoint and provider kind match.
Validation and compatibility coverage
crates/engine/tests/*, crates/engine/src/sync/*, crates/wasm/tests/*, packages/client/src/**/*.test.ts
Tests cover bearer preservation, missing credentials, provider changes, serialization, error classification, and updated bearer-state fixtures.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant SettingsForm
  participant ClientWorker
  participant WasmByoIpfsConfig
  participant Engine
  participant StoredProvider
  SettingsForm->>ClientWorker: save with KEEP_STORED_BEARER
  ClientWorker->>WasmByoIpfsConfig: keepAccessToken = true
  WasmByoIpfsConfig->>Engine: ByoBearer::Keep
  Engine->>StoredProvider: resolve matching held bearer
  StoredProvider-->>Engine: ByoBearer::Set
  Engine-->>SettingsForm: publish result
Loading

Merge Risk: ⚪ Minimal · up to 95dcb

This change adds a way to preserve a stored provider credential during unrelated settings saves without ever exposing it to the browser. The credential-preservation logic, its rejection of contradictory or repointed keep requests, and its client/engine test coverage all appear sound based on the available evidence, and the one flagged documentation concern does not hold up under inspection. The change appears ready to merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 55 functions across 23 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #1535 requires a tri-state bearer intent, Rust-side keep resolution, same-provider endpoint and kind binding, explicit clear support, contradictory-intent rejection, write-only credential handli…
Out of Scope Changes check ✅ Passed The changed engine, wasm, client, and web files support issue #1535. The added re-export, protocol sentinel, form logic, codec changes, compatibility updates, and tests are required to carry and valid…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving a stored provider credential during unrelated settings saves.
Full details: Docstring Coverage

Explanation

Docstring coverage is 72.73% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 55 functions across 23 files. (1 skipped: 1 too large.)

✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/1535-keep-provider-credential-across-settings-save
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/1535-keep-provider-credential-across-settings-save

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@FSM1

FSM1 commented Sep 15, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@FSM1
FSM1 marked this pull request as ready for review September 15, 2026 05:52
…save

The vault settings record round-tripped every field but the provider bearer. The wasm boundary is write-only for that credential, so a read reports only that one is stored. A save publishes the record whole, which made a blank credential field mean no bearer and turned every unrelated settings change into a credential loss.

The bearer is now three-state at every layer. ByoBearer::Keep is a save intent alone: it names no bytes, validate_byo_config refuses it on the encode path and on every request path, and Engine::save_vault_settings resolves it from the provider config the session placement already holds.

A kept bearer is bound to the provider it was stored for. The save must name the same endpoint and the same kind, or it is refused. The keep intent crosses an untrusted realm boundary, so without that binding a host could carry a credential it cannot read on to an endpoint it chose and have the next placement present the member's bearer there.

The wasm constructor refuses a keep intent that also carries bytes, and zeroizes those bytes before it refuses. The web settings form sends the keep intent when the credential field is untouched and the provider is unchanged, which replaces the refusal that asked the member to retype the credential for every save.
@FSM1
FSM1 force-pushed the feat/1535-keep-provider-credential-across-settings-save branch from 95dcb66 to 93d66d6 Compare September 15, 2026 06:00
@FSM1
FSM1 merged commit 32068fc into main Sep 15, 2026
42 checks passed
@FSM1
FSM1 deleted the feat/1535-keep-provider-credential-across-settings-save branch September 15, 2026 06:55
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.

engine: keep a stored provider credential across an unrelated settings save

1 participant