feat: pluggable Store trait for custom storage backends#184
feat: pluggable Store trait for custom storage backends#184lewiscasewell wants to merge 1 commit intoopen-wallet-standard:mainfrom
Conversation
Introduce a minimal key-value Store trait (get/set/remove + optional list) so OWS can work with any storage backend — not just the filesystem. This enables React Native (Keychain/SecureStore), browser (IndexedDB), server (database/Redis), and in-memory (testing) backends without modifying the core wallet, signing, or policy logic. - Add Store trait, StoreError, InMemoryStore, and index helpers to ows-core - Add FsStore in ows-lib (extracts filesystem logic from vault/key_store/policy_store) - Add _with_store variants for all 17 public ops/key_ops functions - Original vault_path signatures unchanged (thin wrappers over _with_store) - Node binding: createOws() factory with JS callback store bridge - Python binding: OWS class accepting a Python store object - 53 new Rust tests + 5 Node integration tests, all 243 existing tests pass No breaking changes to existing API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@lewiscasewell is attempting to deploy a commit to the MoonPay Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 55d97b1. Configure here.
| .ok() | ||
| .and_then(|v| v.coerce_to_string().ok()) | ||
| .and_then(|s| s.into_utf8().ok()) | ||
| .and_then(|u| u.as_str().ok().map(|s| s.to_string())); |
There was a problem hiding this comment.
vaultPath coercion turns undefined into literal string
Medium Severity
When createOws({}) is called with an empty options object (or any object without store or vaultPath), get_named_property::<JsUnknown>("vaultPath") returns Ok(undefined) (NAPI returns undefined for non-existent properties, not an error). Then coerce_to_string() converts JavaScript undefined to the literal string "undefined". This results in FsStore using a relative path ./undefined/ as the vault directory instead of the default ~/.ows/. The same issue occurs with null values. Wallets would be silently stored in the wrong location and existing wallets become invisible.
Reviewed by Cursor Bugbot for commit 55d97b1. Configure here.
| if let Ok(w) = serde_json::from_str::<EncryptedWallet>(&json) { | ||
| return Ok(w); | ||
| } | ||
| } |
There was a problem hiding this comment.
Direct key lookup enables path traversal in FsStore
Low Severity
The new load_wallet_by_name_or_id_with_store adds a "fast path" direct lookup using format!("wallets/{name_or_id}") where name_or_id is user-supplied. For FsStore, this maps to a filesystem path via key_to_path, so a crafted input like ../../etc/secret would resolve to a file outside the vault directory. The original load_wallet_by_name_or_id didn't have this issue because it listed all wallets first and searched in-memory.
Reviewed by Cursor Bugbot for commit 55d97b1. Configure here.


Summary
Introduces a minimal key-value
Storetrait so OWS can work with any storage backend — not just the filesystem. This enables React Native (Keychain/SecureStore), browser (IndexedDB), server (database/Redis), and in-memory (testing) use cases.The trait is 3 required methods:
The library owns key naming (
wallets/{id},keys/{id},policies/{id}) and serialization. The store just moves strings by key.No breaking changes. All 33 existing
vault_path: Option<&Path>functions remain with identical signatures. They become thin wrappers around new_with_storevariants usingFsStore. Default behavior (None→~/.ows/) is unchanged.What's included
ows-core:Storetrait,StoreError,InMemoryStore, index helpers (store_set_indexed/store_remove_indexed)ows-lib:FsStore(filesystem impl extracted from vault/key_store/policy_store),_with_storevariants for all 17 public ops functionscreateOws()factory accepting a JS store object{ get, set, remove, list? }via raw NAPI bridgeOWSclass accepting a Python store objectUsage
Motivation
We're looking at using OWS as the wallet layer in a React Native app, replacing a JS-based wallet stack (bip39 + viem + CryptoJS + per-chain signing libs). The pluggable store is what makes this possible — the app provides Keychain/Keystore-backed storage while OWS handles all the crypto in Rust.
Test plan
cargo test --workspace— 577 tests, 0 failures🤖 Generated with Claude Code
Note
Medium Risk
Refactors persistence and key/policy/wallet CRUD to route through a new
Storeabstraction and adds cross-language store bridges; bugs here could affect wallet/key discovery (indexing) and data durability, though defaults remain filesystem-backed.Overview
Introduces a new pluggable
Storeabstraction inows-core(plus index helpers and anInMemoryStore) so wallets, policies, and API keys can be persisted to non-filesystem backends.Refactors
ows-liboperations and storage modules to add_with_storevariants for wallet/key/policy CRUD and signing flows, with existing vault-path APIs now delegating to a newFsStoreimplementation (including stricter UNIX perms for sensitive data).Extends Node and Python bindings to accept custom store objects: Node adds
createOws({ store, vaultPath })backed by a NAPIJsStorebridge (with index-based fallback whenlistis missing), Python adds anOWSclass backed by aPyStore; new Node integration tests cover custom-store create/list/export/sign behavior.Reviewed by Cursor Bugbot for commit 55d97b1. Bugbot is set up for automated code reviews on this repo. Configure here.