Skip to content

feat(platform-wallet): add SecretStore keyring + encrypted-file secret backends (secrets feature)#3672

Draft
Claudius-Maginificent wants to merge 10 commits into
feat/platform-wallet-sqlite-persistorfrom
feat/platform-wallet-storage-secrets
Draft

feat(platform-wallet): add SecretStore keyring + encrypted-file secret backends (secrets feature)#3672
Claudius-Maginificent wants to merge 10 commits into
feat/platform-wallet-sqlite-persistorfrom
feat/platform-wallet-storage-secrets

Conversation

@Claudius-Maginificent
Copy link
Copy Markdown
Collaborator

DRAFT PR — do not merge until CI has been run and #3625 lands.

STACKED ON #3625 (feat/platform-wallet-sqlite-persistor). The base branch for this PR is feat/platform-wallet-sqlite-persistor, not v3.1-dev. Please diff against that base, and merge this PR only after #3625 lands (or rebase onto v3.1-dev at that point).


Issue being fixed or feature implemented

Wallet apps built on rs-platform-wallet need a place to persist secret material — BIP-39 mnemonics, BIP-32 seeds, xpriv keys — so a wallet can be rehydrated from storage without the user re-entering their phrase every time. The SQLite persister introduced in #3625 deliberately stores only public/correlation state (UTXOs, identities, contacts, token balances). It must never hold secret bytes, and currently has no companion that does.

Imagine you are building a mobile or desktop wallet on top of rs-platform-wallet. You derive a BIP-39 mnemonic at setup, persist the wallet state via the SQLite backend, and your user closes the app. On next launch, you need to rehydrate the wallet — reconstruct its derived keys — without asking the user to re-enter their phrase. You need a place to store that phrase that is: (a) outside the SQLite file, so backups and DB exports stay secret-free; (b) authenticated and encrypted at rest; (c) keyed by wallet identity, not by the wallet file path. This PR delivers that capability as an explicit, opt-in module, behind a Cargo feature that is off by default.


What was done?

New module: platform_wallet_storage::secrets (behind --features secrets)

The secrets Cargo feature (previously a no-op stub at Cargo.toml:110) now activates a complete secrets subsystem under src/secrets/. The feature is not in default — a build that does not opt in compiles exactly as before, with no new dependencies.

SecretStore trait (src/secrets/store.rs)

pub trait SecretStore: Send + Sync {
    fn put(&self, wallet_id: WalletId, label: &str, bytes: &[u8])
        -> Result<(), SecretStoreError>;
    fn get(&self, wallet_id: WalletId, label: &str)
        -> Result<Option<SecretBytes>, SecretStoreError>;
    fn delete(&self, wallet_id: WalletId, label: &str)
        -> Result<(), SecretStoreError>;
}

get returns Option<SecretBytes> — a zeroize-on-drop wrapper, never a bare Vec<u8>. label is validated against ^[A-Za-z0-9._-]{1,64}$ before any backend touches it; wallet_id is a fixed-width 32-byte newtype. An ergonomic put_secret default method accepts &SecretBytes directly. The trait is Send + Sync; a compile assertion mirrors the existing _send_sync_check block.

Zeroizing wrappers (src/secrets/secret.rs)

Two types carry secret material:

  • SecretBytes — net-new, byte-oriented, wraps Zeroizing<Vec<u8>>. Redacting Debug ([REDACTED; N]), no Display/Deref/Serialize, constant-time equality via subtle::ConstantTimeEq, best-effort mlock via region. The type used for seeds, derived keys, AEAD key, and decrypted plaintext.
  • SecretString — a trimmed in-crate fork of the Secret type from dash-evo-tool (src/model/secret.rs), MIT, with attribution preserved. The egui::TextBuffer impl (including its documented take() leak) is deleted in full — this crate has no egui dependency and the leak vector cannot exist by construction. Redacting Debug, no Display/Deref/Serialize, full-capacity zeroize Drop. Used for mnemonics and passphrases. The PartialEq constant-time XOR fold is retained for passphrase UX equality only; its length-leak caveat is documented in rustdoc and it is explicitly not used for any security decision.

The one unsafe block (slice-from-raw-parts in the forked Drop) carries a // SAFETY: proof and a narrowly-scoped #[allow(unsafe_code)]; the crate-wide #![deny(unsafe_code)] is not relaxed.

Error type (src/secrets/error.rs)

SecretStoreError is a concrete thiserror enum — no Box<dyn Error>, no #[non_exhaustive], no secret bytes in any variant, static #[error("...")] strings. This satisfies the existing NFR-4 / TC-082 grep guard. Variants: BackendUnavailable, KeyringLocked, NotFound, Decrypt, InvalidLabel, Io, KdfFailure, VersionUnsupported, WrongPassphrase, InsecurePermissions.

KdfFailure and Decrypt deliberately omit #[source] — the upstream Argon2 and AEAD errors carry no useful non-secret diagnostic and risk leaking structure.

KeyringStore (src/secrets/keyring.rs)

Uses keyring-core 1.0.0 (the split-crate model) plus per-platform credential-store crates. Namespacing: service = "dash.platform-wallet-storage", account = "{wallet_id_hex}:{label}" — two wallets cannot collide, a different app cannot silently read the entry. Keyring item attributes carry only the non-secret wallet/label index.

Fail-closed by design. Headless Linux (no Secret Service), locked collections, and unavailable daemons surface as typed BackendUnavailable / KeyringLocked errors. There is no silent fallback to plaintext or to EncryptedFileStore. The operator selects the backend explicitly. Per-OS caveats (macOS Keychain ACL, Windows DPAPI same-user limitation, headless Linux) are documented in rustdoc on the impl.

EncryptedFileStore (src/secrets/file/)

Argon2id + XChaCha20-Poly1305 vault file, fully under our control:

  • KDF: Argon2id (argon2 0.5.3, pinned) with enforced floors (m ≥ 19 MiB, t ≥ 2, p = 1) and shipped defaults (m = 64 MiB, t = 3). Parameters stored in the file header so future hardening does not strand existing vaults.
  • Salt: 32 bytes from OsRng/getrandom, per-vault, unique, in the header. Never constant, never derived from wallet_id or path.
  • AEAD: XChaCha20-Poly1305 (chacha20poly1305 0.10.1, pinned). No unauthenticated mode. aes-gcm is deliberately absent.
  • Nonce: random 24-byte XNonce freshly generated for every put, stored alongside the ciphertext. Counters are explicitly forbidden (multi-process / restart / file-copy scenario would reuse counter values — catastrophic keystream reuse).
  • AAD: canonical length-prefixed format_version ‖ wallet_id ‖ label. Decryption under a different (wallet_id, label) or a rolled-back format_version fails the Poly1305 tag. Blob-swap and replay attacks are structurally rejected.
  • Tag failure: returns SecretStoreError::Decrypt and exposes zero decrypted-but-unverified bytes. The combined (non-detached) AEAD decrypt API is used so unverified plaintext is never materialized — consistent with the behavioural lesson of RUSTSEC-2023-0096.
  • Passphrase-verification token: a header-stored token allows detecting a wrong passphrase before a full decrypt attempt, without exposing ciphertext bytes.
  • File permissions: created at mode 0600 via O_EXCL + fchmod before any plaintext-derived byte is written (not chmod-after). A pre-existing file with looser permissions surfaces as SecretStoreError::InsecurePermissions — never blindly overwritten.
  • Atomic + durable writes: temp file in the same directory at 0600, write, fsync temp, rename over target, fsync directory. A crash mid-write never truncates the prior vault.
  • Rekey: decrypt-all → re-derive with fresh salt + current params → re-encrypt with fresh per-entry nonces → same atomic replace. No .bak left holding old key material.

MemoryStore (src/secrets/memory.rs)

Test-only backend, gated behind __secrets-test-helpers so it is unreachable from production builds. Stored values are SecretBytes so the zeroize contract is exercised even in tests.

Secrets guard (tests/secrets_guard.rs)

Positive guard test scanning src/secrets/ for tracing::/println!/format! of expose_secret() or plaintext — the complement to tests/secrets_scan.rs, which scans src/sqlite/ and explicitly exempts src/secrets/.

CI: cargo-deny advisories gate (.github/workflows/security-audit-rust.yml, deny.toml)

cargo audit and cargo deny advisories now run with --features secrets in CI so the crypto dependencies (previously unreachable from the default feature set) are covered. All crypto pins are RustSec-clean at adoption (independently verified against the RustSec advisory-db for chacha20poly1305, argon2, zeroize, region, subtle; aes-gcm is not present).

Secrets boundary: unchanged and enforced

The SECRETS.md boundary remains intact. The secrets module lives entirely under src/secrets/. The tests/secrets_scan.rs guard covers src/sqlite/schema/ and migrations/ and exempts src/secrets/ by design — this separation is load-bearing, not stylistic. The new tests/secrets_guard.rs covers the exempt side.

Out of scope for this PR

The SeedProvider trait and the rehydration seam (load_from_persistor signature, wrong-seed gate hardening in rs-platform-wallet/src/manager/load.rs) are not in this PR. They land with the rehydration PR-2 (item S), which depends on this PR as a hard prerequisite. This PR delivers the storage capability; PR-2 delivers the policy that consumes it.


How Has This Been Tested?

The following commands form the gate. CI has not yet been run on this branch (it is a draft); commands are the intended test plan.

# Format and lint
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings

# Default build — must compile without secrets
cargo check -p rs-platform-wallet-storage

# With secrets feature — full test suite
cargo test -p rs-platform-wallet-storage --features secrets

# Without secrets feature — confirm no bleed-through
cargo test -p rs-platform-wallet-storage

# Doc tests
cargo test -p rs-platform-wallet-storage --features secrets --doc

# Secrets scan (SQLite side — must stay green)
cargo test -p rs-platform-wallet-storage secrets_scan

# Positive secrets guard (secrets side — must stay green)
cargo test -p rs-platform-wallet-storage secrets_guard

# Zeroize-on-drop (single-threaded, marked #[ignore] by default)
cargo test -p rs-platform-wallet-storage --features secrets -- --ignored --test-threads=1

# Dependency audit with secrets feature (new CI gate)
cargo deny check advisories --features secrets

The following test categories are covered in tests/secrets_api.rs and tests/secrets_guard.rs:

  • Round-trip via all three backends (put → drop → reopen → get — byte-exact)
  • delete idempotent; missing label → Ok(None)
  • Wrong passphrase → SecretStoreError::Decrypt, zero decrypted bytes returned
  • AAD blob-swap: ciphertext moved to a different (wallet_id, label) slot → rejected
  • Nonce uniqueness across multiple put operations
  • Header version rollback → VersionUnsupported
  • Vault file at looser-than-0600 → InsecurePermissions
  • Crash-mid-write simulation: prior vault intact, no plaintext temp file left
  • Rekey: old-key ciphertext unrecoverable, no .bak retained
  • Debug of SecretBytes/SecretString is redacted — no secret bytes in output
  • MemoryStore unreachable from a --features build that omits __secrets-test-helpers
  • Secrets guard scan: no expose_secret() piped into tracing! or format! in src/secrets/
  • KeyringStore headless-Linux path: fails with typed error, no panic, no plaintext fallback (CI-gated / #[ignore] on headless)

The PR has been through an internal multi-agent review covering security requirements (threat model, crypto construction, memory hygiene, dependency vetting), QA test coverage, and consistency with the codebase's existing patterns. Findings from that review are reflected in the implementation; no CRITICAL or HIGH items remain open against this PR's scope.


Breaking Changes

None. This PR is purely additive:

  • The secrets feature is not in default and never will be without an explicit decision. No existing code path changes.
  • SECRETS.md is updated to reflect the delivered API (not a contract change — the boundary it describes is unchanged and now enforced by more tests).
  • The deny.toml and .github/workflows/security-audit-rust.yml additions are CI-only and do not affect library consumers.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Co-authored by Claudius the Magnificent AI Agent

lklimek and others added 10 commits May 19, 2026 16:03
…ppers, error, validation, MemoryStore

Group A (Tasks 1–3) of the secret-storage feature. All gated behind the
opt-in `secrets` Cargo feature (never enabled by `default`).

Task 1 — `secrets::secret`: `SecretString` (trimmed MIT fork of
dash-evo-tool `Secret`, the egui `TextBuffer`/`take()` leak path deleted
by construction — SEC-REQ-3.8.1/3.8.2) + net-new byte-oriented
`SecretBytes`. Redacting `Debug`, no `Display`/`Deref`/`Serialize`,
full-capacity zeroize on drop, best-effort `region` mlock,
`subtle::ConstantTimeEq` on `SecretBytes`. The only `unsafe` is the
forked full-capacity wipe in `Drop`, confined behind a narrow
`#[allow(unsafe_code)]` + `// SAFETY:` proof — `#![deny(unsafe_code)]`
stays crate-wide (SEC-REQ-4.8).

Task 2 — `secrets::error::SecretStoreError`: concrete `thiserror` enum,
no boxed dyn error (SEC-REQ-4.4 / TC-082), no `#[non_exhaustive]`, no
secret/passphrase/plaintext/source in any variant, static `#[error]`
strings. `secrets::validate`: 32-byte `WalletId` newtype +
`^[A-Za-z0-9._-]{1,64}$` label allowlist, reject-not-sanitize
(SEC-REQ-4.3, CWE-22/20).

Task 3 — `secrets::store::SecretStore` trait (`get` returns
`Option<SecretBytes>`, never bare `Vec<u8>` — SEC-REQ-4.1) +
`MemoryStore` test double, gated by `__secrets-test-helpers` so it is
unreachable from production builds (SEC-REQ-2.3.1/2.3.2). `src/lib.rs`
slot activated; `secrets` feature wires only the RustSec-clean pinned
crypto (argon2=0.5.3, chacha20poly1305=0.10.1, zeroize=1.8.2,
subtle=2.6.1, region=3.0.2, getrandom; keyring-core 4.x split). MSRV
1.92 verified to compile the full dep set (`aes-gcm` omitted).
`Send + Sync` / object-safety compile-asserts added.

Satisfies SEC-REQ 3.1, 3.2, 3.3, 3.5, 3.6, 3.8.1, 3.8.2, 4.1, 4.2,
4.3, 4.4, 4.5, 4.6, 4.8, 2.0.3, 2.3.1, 2.3.2.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…a20-Poly1305 vault

Group B Task 4. `secrets::file::{mod,format,crypto}`:

- Argon2id KDF (`argon2 0.5.3`): floors m≥19456 KiB / t≥2 / p=1 enforced
  before any derivation; shipped default 64 MiB / t=3; params + 32-byte
  CSPRNG salt stored in the versioned header (SEC-REQ-2.2.1/.2/.3/.4).
- XChaCha20-Poly1305 (`chacha20poly1305 0.10.1`): fresh random 24-byte
  nonce per `put` (counter forbidden); combined decrypt so no
  unverified plaintext is ever materialized (SEC-REQ-2.2.5/.6/.8).
- AAD = canonical length-prefixed `format_version‖wallet_id‖label`,
  defeating blob-swap / version-rollback (SEC-REQ-2.2.7).
- Self-describing magic+version header; unknown version refused, fail
  closed (SEC-REQ-2.2.9).
- 0600 at creation via O_EXCL + fchmod before any ciphertext byte;
  pre-existing loose perms refused; atomic temp→fsync→rename→dir-fsync;
  temp holds only ciphertext, removed on failure (SEC-REQ-2.2.10/.11).
- Atomic rekey: fresh salt + fresh per-entry nonces, no `.bak`
  (SEC-REQ-2.2.12). Passphrase held in `SecretString`, never persisted,
  zeroized on drop; derived key recomputed per op, never retained
  (SEC-REQ-2.2.13).

Satisfies SEC-REQ 2.0.1, 2.0.2, 2.0.4, 2.2.1–2.2.13, 4.1.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…ring-core 4.x split)

Group B Task 5. `secrets::keyring::KeyringStore` over the keyring 4.x
split: `keyring-core 1.0.0` API + per-platform store crates
(linux-keyutils / dbus-secret-service / apple-native / windows-native),
all exact-pinned, RustSec-clean, MSRV-1.92-verified.

- Namespacing: service `dash.platform-wallet-storage`, account
  `{wallet_id_hex}:{label}` — two wallets cannot collide, a different
  app cannot silently read; only the non-secret index appears in
  keyring attributes (SEC-REQ-2.1.2, CWE-312).
- Fail-closed: headless / no Secret Service / no D-Bus → typed
  `BackendUnavailable`; locked → typed error. Never `unwrap`, never a
  silent plaintext / weaker-store fallback (SEC-REQ-2.1.3/.4 / AR-4).
- keyring-core's bare `Vec<u8>` from `get_secret` is wrapped into
  `SecretBytes` and the intermediate zeroized immediately
  (SEC-REQ-3.1/4.1).
- Per-OS threat-coverage rustdoc on the type (SEC-REQ-2.0.4 / 2.1.3).

Backend selection is an explicit operator decision — no auto-fallback
between KeyringStore and EncryptedFileStore (SEC-REQ-2.1.3 / AR-4).

Satisfies SEC-REQ 2.0.1, 2.0.4, 2.1.1, 2.1.2, 2.1.3, 2.1.4.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…egration tests

Group B Task 6.

`tests/secrets_guard.rs` (SEC-REQ-4.5.1): positive string-level scan of
`src/secrets/` asserting no logging/formatting sink
(`tracing::*`/`println!`/`format!`/`panic!`/…) is paired with an
`expose_secret()` result — the guard `tests/secrets_scan.rs`
deliberately does NOT cover this tree. Green on the clean tree; fails
the moment a secret is routed to a sink.

`tests/secrets_api.rs`: `get` returns `Option<SecretBytes>` (type
binding, never `Vec<u8>` — SEC-REQ-4.1); `dyn SecretStore`
object-safety / positive build guard (SEC-REQ-4.5); no boxed dyn error
in `src/secrets/` (TC-082 parity, comment-aware); error `Display` is
static and secret-free (SEC-REQ-2.0.1/3.3, CWE-209); wrapper `Debug`
redacted at the boundary (SEC-REQ-3.3). `MemoryStore` intentionally
unreachable from this external test crate (SEC-REQ-2.3.1).

Satisfies SEC-REQ 4.5, 4.5.1.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…secrets crypto deps

Group B Task 8 (SEC-REQ-4.7). The existing `rustsec/audit-check`
already audits the full `Cargo.lock` — which now pins the
`secrets`-gated crypto (argon2/chacha20poly1305/zeroize/subtle/region/
keyring-core + per-platform stores), so they are advisory-checked even
though `default` does not enable `secrets`. This adds a `cargo-deny
check advisories --all-features` job so the feature-conditional
dependency graph is exercised explicitly, plus a workspace `deny.toml`
(advisory ignore kept in sync with `.cargo/audit.toml`).

Locally verified: `cargo audit` exits 0; none of the secrets crypto
pins carry any RustSec advisory (confirms Smythe §7 first-hand). The
only flagged item, RUSTSEC-2025-0141 (bincode unmaintained), is a
pre-existing unrelated wasm-sdk/dpp dependency, not in the secrets
path.

Satisfies SEC-REQ 4.7.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…d atomic vault write

C1 (HIGH, Marvin QA-001): a `put`/`get`/`delete`/`rekey` against an
EXISTING vault with a passphrase deriving a DIFFERENT key than the
vault was created with previously wrote a mismatched-key entry and
returned Ok, producing an unreadable mixed-key vault. The header now
carries a passphrase-verification token: an XChaCha20-Poly1305 seal of
a fixed constant under the header-Argon2id-derived key, AAD-bound to
`(format_version, wallet_id, "\0verify")` (the leading-NUL label is
disjoint from every allowlisted entry label, so the token can never
alias a real slot). Every operation on an existing vault derives the
key from the supplied passphrase and verifies the token FIRST; a
mismatch fails the Poly1305 tag (constant-time, no extra compare, no
plaintext on failure) and returns `SecretStoreError::WrongPassphrase`
before any entry is read, written, or deleted. New vaults write the
token at creation; `rekey` verifies the old token and writes a fresh
one. `format_version` bumped 1→2; v1/v2 cross-reads fail closed via
the existing `VersionUnsupported` path.

C6 (LOW, Smythe SEC-RA-001): `write_vault` no longer swallows the
directory-fsync result — it is propagated as a typed error so the
atomic temp→fsync→rename→dir-fsync chain (SEC-REQ-2.2.11) is fully
enforced.

C7 (LOW, Marvin QA-004): the temp file now uses a unique name
(`pid` + monotonic counter) created with `O_EXCL` and the destination
is never pre-removed, so a crash can never leave the vault absent and
concurrent writers cannot collide on a fixed temp name. The atomic
rename + fsync ordering is unchanged.

Tests (red→green, file/mod.rs): wrong-pass `put` to existing vault ⇒
`Err(WrongPassphrase)` + vault still readable with the correct pass +
rejected slot never written; wrong-pass `get`/`delete` ⇒
`Err(WrongPassphrase)` + vault unmutated; correct pass round-trips
unchanged. The two wrong-pass tests were FAILED before this fix and
pass after; format (de)serialize round-trips the token fields.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…ringLocked; correct keyring-core attribution

C3 (MED, Adams PROJ-002 / Marvin QA-003): `map_keyring_err` collapsed
keyring-core's `NoStorageAccess` into `BackendUnavailable`, leaving
`SecretStoreError::KeyringLocked` dead. Per keyring-core 1.0.0 docs,
`NoStorageAccess` covers the locked-collection case ("it might be that
the credential store is locked"), so it now maps to `KeyringLocked`,
enabling the unlock-retry UX (SEC-REQ-2.1.4). Genuinely-absent backends
(`NoDefaultStore` / `PlatformFailure`) stay `BackendUnavailable`.
Added `locked_keyring_maps_to_keyring_locked` asserting the locked,
absent, and not-found mappings.

C5 (LOW, Adams PROJ-003 / Marvin QA-004): the module header said
"keyring-core 4.x split" — inaccurate. Reworded to state the lib is
`keyring-core 1.0.0` plus the per-platform store crates; the `keyring`
4.x crate is the sample CLI and is not a dependency. No dependency
change.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…roizes on drop

C4 (MED, Smythe SEC-RA-002 / Adams PROJ-004 / Marvin QA-002): the
rustdoc claimed stored values sit in `SecretBytes`, but the map held a
bare `Vec<u8>` that never zeroized — code contradicted the doc. Fixed
the code (not the doc): the backing map is now
`HashMap<(WalletId,String), SecretBytes>`, closing SEC-REQ-2.3.2 so
even test memory is wiped on drop. Added `stored_value_is_zeroizing_
wrapper` (type-binding assertion) + a `needs_drop::<SecretBytes>()`
compile-time guard.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…rgo.toml comment

C5 (LOW, Adams PROJ-003 / Marvin QA-004): the per-platform-store
dependency comment said "keyring-core 4.x split". Reworded to state
accurately that `keyring-core 1.0.0` is the API and the per-platform
crates provide the backends (the `keyring` 4.x crate is the sample CLI
and is intentionally not depended on). No dependency change.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…etStore API

C2 (MED, Adams PROJ-001): the trait sketch was stale/dangerous —
`get -> Option<Vec<u8>>` (the exact CRITICAL leak SEC-REQ-4.1 forbids)
and the false "feature flag exists today but flips no code" line.
Rewritten to the delivered API: `get -> Result<Option<SecretBytes>,
SecretStoreError>`, accurate `put`/`delete` signatures, the real
backends (KeyringStore/EncryptedFileStore/MemoryStore with their
fail-closed / gating semantics), and the now-true statement that
enabling `secrets` activates the module. Present-state only, no
history narration; no forbidden token introduced into
`src/sqlite/schema/` or `migrations/`.

Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6e14d71-0e56-4ab6-af76-b9a2ab8e44a7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/platform-wallet-storage-secrets

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 and usage tips.

@Claudius-Maginificent Claudius-Maginificent changed the title feat(platform-wallet-storage): SecretStore — keyring + encrypted-file secret backends (secrets feature) feat(wallet-storage): SecretStore — keyring + encrypted-file secret backends (secrets feature) May 19, 2026
@Claudius-Maginificent Claudius-Maginificent changed the title feat(wallet-storage): SecretStore — keyring + encrypted-file secret backends (secrets feature) feat(platform-wallet): SecretStore — keyring + encrypted-file secret backends (secrets feature) May 19, 2026
@Claudius-Maginificent Claudius-Maginificent changed the title feat(platform-wallet): SecretStore — keyring + encrypted-file secret backends (secrets feature) feat(platform-wallet): add SecretStore keyring + encrypted-file secret backends (secrets feature) May 19, 2026
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.

2 participants