Skip to content

v0.21.3

Choose a tag to compare

@github-actions github-actions released this 01 Sep 18:42
· 6 commits to main since this release

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_reported with allow_fallback from the Enable fallback setting (on by default), so any primary failure walked the chain and wrote to credentials.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_blocking asks the selected backend for both of its lookup keys and, on a miss, returns VaultEntryMissing — it never consulted the encrypted file, whatever Enable fallback was set to, because that setting only ever reached the SecretManager chain and a Vault password 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_enabled is one predicate governing the read and the write offer together, and applies the same test SecretManager::build_from_settings applies before appending EncryptedFileBackend, so the setting cannot mean one thing per password source. Only the miss path falls back: the Err arms still report BackendNotConfigured and 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-cli exit except Invalid credentials/wrong password into "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 in secret/status.rs now share classify_show_failure, which separates "opened, entry absent" from "did not open"; the second is an error that names the database instead of prompting.
    • pass treated any non-zero pass show as a miss and discarded stderr, so an uninitialised store, a missing GPG key and a locked gpg-agent were indistinguishable from an empty store — and it was the only backend with no readiness check (pass --version succeeds 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 SecretManager chain, so a locked database could serve a password out of libsecret or credentials.enc with 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.
  • 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_backend the 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 like MacOsKeychain. It now uses the same readiness verdict the Secrets page shows, with SecretBackendType::display_name for 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 pass readiness probe read the ambient $PASSWORD_STORE_DIR while 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.
  • The Bitwarden master password was demoted out of its Zeroizing wrapper before use — the unlock handler wrapped it to be wiped on drop, then made two bare String copies 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 keepaliveironrdp-session 0.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 any ActiveStage::process error 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 registers drdynvc and 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 next ironrdp-session bump. Still open and not covered: IronRDP#1629, an auto-detect request during the licensing exchange, which happens in ironrdp-connector before 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 Down and limited-connectivity paths announced without checking whether anything was at stake, where the Up path already did. All three now go through should_announce, counted through open_session_count (a detached window or external viewer counts, not just a tab). The socket sweeps still run on every transition, since a stale ControlMaster is 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_settings moved to EncryptedFileBackend (#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 — the Vault password 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 bw that is installed but not logged in has a version. Selecting a not-ready backend is no longer accepted in silence: a new win.recheck-secret-backend action re-checks after settings are saved, so the banner reflects the backend in force rather than the one selected at launch.

  • SecretManager::retrieve logs 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 through CredentialResolver, which would have to carry it too.

  • The pass row in the backend selector is spelled passdisplay_name() returned "Pass", so the row read as a product by that name while its own description, its status label and the documentation all say pass, 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 --version or status call per backend with a 5-second ceiling. The two file backends answer from local state alone, so backend_needs_probe skips the probe for them entirely; two tests pin it against backend_readiness so a backend cannot be exempted from a probe it actually needs, or made to wait for one it does not.

Documentation

  • docs/BITWARDEN_SETUP.md told Flatpak users to log in somewhere RustConn does not read (issue #312) — the guide said to run bw login in a Local Shell tab, but that tab is a host shell spawned through flatpak-spawn --host, which does not carry the sandbox environment across. bw resolves its state directory from $XDG_CONFIG_HOME, which Flatpak sets per-application, so bw run by RustConn and bw run from a Local Shell tab used different data.json files — 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 (run bw inside the sandbox, or pin BITWARDENCLI_APPDATA_DIR), a bw status check, 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 printed export BW_SESSION=… and then said session keys are automatic — the export cannot work, since RustConn reads BW_SESSION from its own environment fixed at launch.

  • docs/BUILD.md records that the headless crates run on Android under Termuxrustconn-core and rustconn-cli build 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.md and docs/BITWARDEN_SETUP.md updated for the read/write asymmetry — the architecture section described a single "fallback chain" and quoted a get_available_backend helper 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-targets is clean against the refreshed lock file.

Installation

Flatpak (Recommended)

flatpak install flathub io.github.totoshko88.RustConn

Snap

sudo snap install rustconn

Debian/Ubuntu (.deb from this release)

sudo dpkg -i rustconn_0.21.3_amd64.deb
sudo apt-get install -f  # Install dependencies if needed

Fedora (.rpm from this release)

sudo dnf install rustconn-0.21.3-1.fc44.x86_64.rpm

AppImage

chmod +x RustConn-0.21.3-x86_64.AppImage
./RustConn-0.21.3-x86_64.AppImage

macOS (Homebrew)

brew tap totoshko88/rustconn
brew install rustconn
open $(brew --prefix)/opt/rustconn/RustConn.app

All 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 rustconn

Arch Linux (AUR)

yay -S rustconn

FreeBSD (Ports)

pkg install rustconn

Full installation guide: https://github.com/totoshko88/RustConn/blob/main/docs/INSTALL.md