The shared SecretStore interface separates credential storage from application
logic. Desktop and mobile builds use native credential services through Tauri.
Browser builds route secret-store operations to the server's encrypted vault
rather than browser storage.
For database keys, portable exports and restore behavior, see database encryption and backups.
Profiles wrap the platform store with a fixed
ScopedSecretStore namespace. New profiles prefix logical keys with
profile:<uuid>:; only the adopted legacy profile keeps existing unprefixed
keys. This includes add-on fallback keys, profile lock records, and
Connect/device credentials. Web profiles share one underlying encrypted vault
instance. Password verification and recovery are described in the profile
architecture.
The Tauri implementation of the shared SecretStore contract uses
keyring-core and explicit platform backends:
| Platform | Backend |
|---|---|
| macOS | Apple login Keychain (apple-native-keyring-store, keychain) |
| iOS | Apple protected Keychain (apple-native-keyring-store, protected) |
| Android | Android Keystore encryption with encrypted SharedPreferences (android-native-keyring-store) |
| Windows | Windows Credential Manager (windows-native-keyring-store) |
| Linux / other configured desktop Unix targets | D-Bus Secret Service (zbus-secret-service-keyring-store) |
Credentials include authentication tokens, provider API keys, and device-sync secrets. Existing service identifiers and account names remain compatible with previous desktop storage. Initialization is lazy, with successful stores cached. Missing or locked native credential services return errors, with no plaintext or in-memory fallback. Linux requires a session D-Bus and an available Secret Service provider.
Sources: native store, shared contract.
The Android backend crate implements encryption and credential persistence. Our JNI bridge only supplies the Java VM and application context before Tauri starts; it does not implement a custom credential store. Its JNI types match the backend's initialization API.
The crate stores ciphertext in shared_prefs/keyring-default.xml. Its
encryption key remains in Android Keystore and cannot be carried to another
device with an ordinary app-data backup. Android Keystore stores cryptographic
keys; arbitrary API-token strings remain encrypted outside it.
The Android manifest selects backup rules that exclude keyring-default.xml
from legacy full backup, cloud backup, and device-to-device transfer. Tauri
stores its database directly in Android dataDir, so the rules also exclude the
root domain: the database, encryption marker, maintenance files, and internal
backups cannot be transferred without their device-bound key. This exclusion
applies whether database encryption is enabled or disabled. Use Wealthfolio’s
portable export and restore to move portfolio data to another device;
credentials must be re-entered or recreated.
Android database encryption uses this persistent store, with an immediate key read-back before conversion. Enabling/disabling rebuilds the runtime in place, as on iOS; a process restart subsequently reopens the database with the retained key. Disabling keeps the key so older encrypted backups remain readable.
Sources: manifest, legacy backup rules, extraction rules.
The Axum server uses FileSecretStore, a JSON vault encrypted with
ChaCha20-Poly1305 and a fresh nonce per write. The required master key comes
from WF_SECRET_KEY or WF_SECRET_KEY_FILE. Both inputs use the same decoder
and HKDF-SHA256 derivation, with separate wealthfolio-jwt and
wealthfolio-secrets labels for session signing and vault encryption. File
input is read once at startup. Native credential services are not required on
the server host.
WF_SECRET_FILE selects the encrypted vault location. Its default is
secrets.json alongside the database. File writes use the operating system's
normal filesystem behavior: existing ownership, permissions, ACLs, symlinks,
hard links, and individual file mounts are preserved. File creation follows
platform permissions and the process umask.
A mutex serializes operations through one store instance. Writes happen in place and are not crash-atomic; independent processes must not write the same vault.
Sources: server store, key configuration, key derivation.
Existing derived-key encrypted files keep their format and key derivation. Supported raw-key files migrate to derived-key encryption. Legacy plaintext files remain readable and are encrypted on their next successful credential write. Empty files are treated as empty vaults. Invalid nonce lengths return errors instead of panicking. A malformed encrypted envelope is not treated as plaintext.
Vault read/decryption failures are reported by secret operations rather than blocking unrelated server features at startup. Failed reads do not trigger an automatic reset. An interruption during a write can still leave an incomplete file, so backups remain necessary.
Keep the master key in a separately protected recovery location from the vault backup. Losing it prevents decryption. Switching from environment input to file input must use the same key; changing its value is not a key-rotation procedure and also affects session signing and OIDC logout-token encryption.
For configuration and commands, see the server README and self-hosting guide.