v0.21.3
What's Changed
A release spent on making the secret backends honest: a password the selected
backend refused is no longer redirected to a local file behind the user's back,
where a password went is now a question the user is asked and an answer the
connect path honours, every backend reports the same set of failures the same
way, and the Secrets page and startup banner say which backend is in force and
whether it can actually store a password. Two unrelated fixes ride along — an
embedded RDP session that a Windows 11 keepalive could kill, and network toasts on
a window with nothing open.
Fixed
-
A password the selected backend refused was moved into the encrypted file without asking, where the connect path never looks — saving went through
SecretManager::store_reportedwithallow_fallbackfrom the Enable fallback setting (on by default), so any primary failure walked the chain and wrote tocredentials.enc, while connect-time resolution queries the selected backend alone. A locked Bitwarden vault therefore produced a password that was saved and, from the connection's view, missing — "Vault entry not found. You will be prompted for a password" for a password on disk the whole time. Saving now targets the selected backend only; a refusal opens a dialog naming the backend and its cause, and offers the encrypted file as a deliberate choice. -
A password in the encrypted file was still not found at connect time, so the offer above had nowhere to put one — the write half of the fix above is only half.
resolve_credentials_blockingasks the selected backend for both of its lookup keys and, on a miss, returnsVaultEntryMissing— it never consulted the encrypted file, whatever Enable fallback was set to, because that setting only ever reached theSecretManagerchain and aVaultpassword source does not go through the chain. A password put in the encrypted file by the old silent redirect, or by the new dialog's Save to This Computer, was therefore still reported missing: the same "saved and missing at the same time", now with the user's consent on it. Both miss paths — the KeePass one and the non-KeePass one — now read the encrypted file when Also read from the encrypted file is on, under the same lookup keys the write used rather than keys derived independently, since a key computed twice is a key that can disagree.encrypted_file_fallback_enabledis one predicate governing the read and the write offer together, and applies the same testSecretManager::build_from_settingsapplies before appendingEncryptedFileBackend, so the setting cannot mean one thing per password source. Only the miss path falls back: theErrarms still reportBackendNotConfiguredand consult nothing, because a store that could not be read has not said the password is absent. -
Secret backends disagreed on how to report a failure, and three of them called an unreadable store a missing entry — a group of fixes making every backend answer the same way:
- KeePassXC turned every non-zero
keepassxc-cliexit exceptInvalid credentials/wrong passwordinto "entry not found", so a corrupt or unsupported database, a wrong--key-file, or a hardware key awaiting a touch all read as "no such password". The three KDBX readers insecret/status.rsnow shareclassify_show_failure, which separates "opened, entry absent" from "did not open"; the second is an error that names the database instead of prompting. passtreated any non-zeropass showas a miss and discarded stderr, so an uninitialised store, a missing GPG key and a lockedgpg-agentwere indistinguishable from an empty store — and it was the only backend with no readiness check (pass --versionsucceeds on an uninitialised store). Only pass's own "is not in the password store" is a miss now.- KeePass at resolve time logged a failure and fell through to the generic
SecretManagerchain, so a locked database could serve a password out of libsecret orcredentials.encwith nothing saying the chosen database was never consulted. KeePass now answers like every other backend on that path, and the two cases are separated rather than collapsed: a database that could not be read is reported and nothing else is consulted, while a database that opened and does not hold the entry falls back to the encrypted file exactly as the other backends do. That distinction is the fix — what was wrong was the locked database, not the miss. The narrowing that remains is that libsecret is no longer consulted on a KeePass miss, only the encrypted file; Copy Passwords… in Settings ▸ Secrets is what moves entries between stores.
- KeePassXC turned every non-zero
-
Secret status was reported to the wrong words, the wrong store, or not at all — a cluster of Secrets-page and banner fixes:
- "Secret Backend Not Configured" was shown to people whose backend was merely locked — the dialog discarded the
required_backendthe result carried. It now names the backend and says the password could not be read. - The startup banner was built from
BackendAvailability(three keyring-shaped variants), so a Bitwarden vault that was not logged in came out as "keyring client is not installed" and printed Rust variant names likeMacOsKeychain. It now uses the same readiness verdict the Secrets page shows, withSecretBackendType::display_namefor the name. - The Passbolt and Pass status lines stayed on "Detecting…" forever, because they set a label only when the probe returned a status — and a missing CLI returns nothing.
- The
passreadiness probe read the ambient$PASSWORD_STORE_DIRwhile the backend uses the configured directory, so a custom store was reported "Not initialized" while healthy. The probe now resolves the directory in the backend's own order, keyed into the detection cache.
- "Secret Backend Not Configured" was shown to people whose backend was merely locked — the dialog discarded the
-
The Bitwarden master password was demoted out of its
Zeroizingwrapper before use — the unlock handler wrapped it to be wiped on drop, then made two bareStringcopies that moved into closures and dropped unwiped, leaving it in freed heap. Both copies stay wrapped now. The handler also logged the resulting session key's length — bruteforce metadata it had declined to log two statements earlier for the password; the field is gone. -
An embedded RDP session against a Windows 11 host could be killed by the server's own keepalive —
ironrdp-session0.11 decodes every PDU on the MCS message channel as an auto-detect request, but that channel also carries Heartbeat PDUs, which Windows 11 sends every one to two seconds. The first failed the security-header check, came back as a decode error, and RustConn turned anyActiveStage::processerror into a dead session — so it ended seconds after connecting as a plain "Session error". Reachable on every embedded RDP connection to such a host, since RustConn always registersdrdynvcand one static channel is enough for Windows to allocate a real message channel. A PDU the session layer cannot decode is now survivable when it arrived on the message channel (auto-detect is advisory; a client implementing neither Heartbeat nor multitransport need not answer). The guard re-derives the channel from the frame rather than matching on the error kind, so a failure to parse the MCS framing itself stays fatal; five tests pin the narrowness. Credit to issue #262. Temporary: IronRDP#1814 merged the real fix upstream but is unpublished, so the guard carries the gap until the nextironrdp-sessionbump. Still open and not covered: IronRDP#1629, an auto-detect request during the licensing exchange, which happens inironrdp-connectorbefore this session loop exists. -
An idle window with no connections showed network warnings during a Wi-Fi flap — a four-signal flap produced three toasts in two seconds on a window with nothing open, the first reading "Network disconnected — active sessions may be interrupted" when there were none. The debounce is right and unchanged; what was wrong is that the
Downand limited-connectivity paths announced without checking whether anything was at stake, where theUppath already did. All three now go throughshould_announce, counted throughopen_session_count(a detached window or external viewer counts, not just a tab). The socket sweeps still run on every transition, since a staleControlMasteris worth closing regardless. This also silences the launch-time "network down" toast that affects nobody.
Changed
-
"Enable fallback" is now "Also read from the encrypted file", and governs reads only — the description said "Use libsecret if the primary backend is unavailable", naming a store that stopped being the fallback when
build_from_settingsmoved toEncryptedFileBackend(#201) and was never updated. It now names the encrypted file, is no longer platform-conditional, and says what it does: look there as well when resolving a password, so entries saved before a backend change keep working. Two caveats worth stating, since the old label's problem was a description that had drifted from the code. Making the new one true took a code change, not just a rewording — theVaultpassword source never reached the chain the setting governed, and that is the second Fixed entry above. And where a write goes is no longer part of this setting at all, which is the first. -
The Secrets page reports whether the selected backend can actually store a password — one Status line for every backend, replacing an Availability row shown only for the system keyring (four of eight backends had none). It distinguishes "ready", "needs something from you" (not logged in, locked, no database chosen, no keyring answering) and "not installed" — the distinction the Version row cannot make, since a
bwthat is installed but not logged in has a version. Selecting a not-ready backend is no longer accepted in silence: a newwin.recheck-secret-backendaction re-checks after settings are saved, so the banner reflects the backend in force rather than the one selected at launch. -
SecretManager::retrievelogs when a fallback backend answered instead of the selected one — "my backend works" and "my backend is broken and everything is quietly coming from a local file" were indistinguishable in a log. A read served by anything other than the first entry in the chain now warns with both backend ids. A field on the return type was considered and dropped: nothing in the interface is ready to surface it, and the remaining chain reads go throughCredentialResolver, which would have to carry it too. -
The
passrow in the backend selector is spelledpass—display_name()returned "Pass", so the row read as a product by that name while its own description, its status label and the documentation all saypass, which is the program's actual name. -
The startup backend check no longer probes seven command-line clients to answer a question that has no probe in it — the check now shares the Secrets page's readiness verdict, and that verdict comes from
detect_secret_backends, which spawns a--versionorstatuscall per backend with a 5-second ceiling. The two file backends answer from local state alone, sobackend_needs_probeskips the probe for them entirely; two tests pin it againstbackend_readinessso a backend cannot be exempted from a probe it actually needs, or made to wait for one it does not.
Documentation
-
docs/BITWARDEN_SETUP.mdtold Flatpak users to log in somewhere RustConn does not read (issue #312) — the guide said to runbw loginin a Local Shell tab, but that tab is a host shell spawned throughflatpak-spawn --host, which does not carry the sandbox environment across.bwresolves its state directory from$XDG_CONFIG_HOME, which Flatpak sets per-application, sobwrun by RustConn andbwrun from a Local Shell tab used differentdata.jsonfiles — the terminal reported the vault unlocked while Settings → Secrets said "You are not logged in." The guide gains a "Where the CLI keeps its login state" section, two Flatpak login recipes that write where RustConn reads (runbwinside the sandbox, or pinBITWARDENCLI_APPDATA_DIR), abw statuscheck, and a troubleshooting entry. Snap and native installs were never affected and are called out as such. Also removed a contradiction in Step 3, which printedexport BW_SESSION=…and then said session keys are automatic — the export cannot work, since RustConn readsBW_SESSIONfrom its own environment fixed at launch. -
docs/BUILD.mdrecords that the headless crates run on Android under Termux —rustconn-coreandrustconn-clibuild and run there with default features off (issue #129). Known-to-work rather than supported, since no CI job covers it: what works is connection management, not opening sessions — the GTK4 GUI does not target Android. -
docs/ARCHITECTURE.md,docs/USER_GUIDE.mdanddocs/BITWARDEN_SETUP.mdupdated for the read/write asymmetry — the architecture section described a single "fallback chain" and quoted aget_available_backendhelper that does not exist; it now states why reads may fall back and writes may not. The user guide's Secrets page listing gained the Status row and lost the claim that fallback means libsecret. The Bitwarden guide gained a Status table mapping each state to what to do, and its "Enable fallback" section was rewritten with a note about how earlier releases behaved, since anyone with passwords the old silent redirect put in the encrypted file needs to know where to look.
Dependencies
- Updated: libredox 0.1.21 → 0.1.23, ppmd-rust 1.4.0 → 1.4.1, smallvec 1.15.2 → 1.16.0. All semver-compatible transitive updates from
cargo update;cargo check --all-targetsis clean against the refreshed lock file.
Installation
Flatpak (Recommended)
flatpak install flathub io.github.totoshko88.RustConnSnap
sudo snap install rustconnDebian/Ubuntu (.deb from this release)
sudo dpkg -i rustconn_0.21.3_amd64.deb
sudo apt-get install -f # Install dependencies if neededFedora (.rpm from this release)
sudo dnf install rustconn-0.21.3-1.fc44.x86_64.rpmAppImage
chmod +x RustConn-0.21.3-x86_64.AppImage
./RustConn-0.21.3-x86_64.AppImagemacOS (Homebrew)
brew tap totoshko88/rustconn
brew install rustconn
open $(brew --prefix)/opt/rustconn/RustConn.appAll dependencies (GTK4, libadwaita, VTE, Adwaita icons) are installed automatically.
Requires macOS 13 (Ventura) or later.
OBS Repositories
Packages available at: https://build.opensuse.org/package/show/home:totoshko88:rustconn/rustconn
# Debian 13 (Trixie)
echo 'deb http://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Debian_13/ /' \
| sudo tee /etc/apt/sources.list.d/rustconn.list
curl -fsSL https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Debian_13/Release.key \
| gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/rustconn.gpg > /dev/null
sudo apt update && sudo apt install rustconn
# Ubuntu 24.04 LTS (Noble)
echo 'deb http://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_24.04/ /' \
| sudo tee /etc/apt/sources.list.d/rustconn.list
curl -fsSL https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_24.04/Release.key \
| gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/rustconn.gpg > /dev/null
sudo apt update && sudo apt install rustconn
# Ubuntu 26.04 LTS (Resolute)
echo 'deb http://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_26.04/ /' \
| sudo tee /etc/apt/sources.list.d/rustconn.list
curl -fsSL https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_26.04/Release.key \
| gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/rustconn.gpg > /dev/null
sudo apt update && sudo apt install rustconn
# Fedora 44
sudo dnf config-manager addrepo --from-repofile=https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Fedora_44/home:totoshko88:rustconn.repo
sudo dnf install rustconn
# Fedora 43
sudo dnf config-manager addrepo --from-repofile=https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Fedora_43/home:totoshko88:rustconn.repo
sudo dnf install rustconn
# openSUSE Tumbleweed
sudo zypper ar https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/openSUSE_Tumbleweed/ rustconn
sudo zypper ref && sudo zypper in rustconn
# openSUSE Leap 16.0
sudo zypper ar https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/openSUSE_Leap_16.0/ rustconn
sudo zypper ref && sudo zypper in rustconnArch Linux (AUR)
yay -S rustconnFreeBSD (Ports)
pkg install rustconnFull installation guide: https://github.com/totoshko88/RustConn/blob/main/docs/INSTALL.md