Skip to content

Releases: Elevate08/qs-bitwarden-cli

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 01 Sep 01:30

Added

  • Server region on the login screen: US (the default), EU, or Custom. EU points the CLI at https://vault.bitwarden.eu; Custom reveals the server URL field for self-hosted Bitwarden and Vaultwarden. The choice applies to email/password, API key and the interactive terminal login alike, so an EU account no longer has to be told its own server's address. Refs #6.

Changed

  • Dependabot proposes lockfile-only cargo updates, so a bump moves agent/Cargo.lock within the bounds agent/Cargo.toml already allows and never raises a floor on its own. The crypto crates are coupled -- ssh-key, rsa and the traits they re-export have to move together or cargo resolves two generations side by side and nothing compiles -- and README's Dependencies section records why that upgrade is a manual, all-at-once edit, along with the binary rebuild every accepted bump needs.

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 23:05

Opt-in SSH agent. Implements #1.

Added

  • SSH agent (sshAgentEnabled, off by default). Serves the SSH keys in your vault to ssh, Git and ssh-keygen -Y sign while the vault is unlocked. Ed25519 and RSA SHA-2, over a socket in $XDG_RUNTIME_DIR that only your own UID may use. Private keys live in a separate helper process, never on disk and never in QML, and are dropped on lock, logout and exit.
  • Every signature is approved in the panel, which names the key, its fingerprint and the program asking. One approval can cover further signatures from the same program and key for sshAgentApprovalWindowSec seconds (default 120), so a twenty-commit rebase is one prompt. Live approvals are listed with the time they have left and can be revoked.
  • A cooldown after two unanswered prompts, five minutes, during which signing is refused without reopening the panel. A banner says so and counts down; Resume Signing Now ends it early.
  • Public keys are projected to ~/.local/share/qs-bitwarden-cli/ssh/*.pub, public material only, so Git SSH signing has the file paths it requires.
  • Client routing through one plugin-owned UWSM fragment, written when the agent is enabled and removed when it is disabled, taking effect at the next login. An agent that already owns SSH_AUTH_SOCK is named and confirmed before it is replaced; a file this plugin did not write is reported and left alone.
  • sshAgentUnlockOnDemand (off by default) lets an identity listing raise the unlock prompt when the vault is locked with no keys loaded. Signing a key the helper already holds always prompts, with or without it.
  • Remove Plugin Data on the settings screen clears the keyring entries, learned suggestions and exported public keys in one confirmed action. Your vault is untouched.
  • sshAgentStatus diagnostics: which helper is running, whether its checksum matched, and what the panel believes about client routing.
  • The helper ships as a reproducibly built, checksum-validated binary, with releases carrying a GitHub build-provenance attestation, an SBOM and a dependency report. Any validation failure disables SSH support alone and leaves the rest of the plugin working; a locally built helper is used as a fallback and says so on a banner.

Security

  • The vault read is split before it reaches the panel: SSH private material goes to the helper over a private FIFO carrying a per-load nonce, and QML receives a sanitized list with it removed.
  • A signature is refused unless the vault is unlocked at the epoch its key was loaded under, so a lock racing a load, an approval or a signature cannot leave a key usable.
  • Agent forwarding is not supported in this release; a forwarded request is labelled as such in the prompt, because the process it names is not the one that would use the signature.
  • ecdsa keys are not supported.

v1.4.1 — icon fix

Choose a tag to compare

@Elevate08 Elevate08 released this 31 Aug 01:32

A cosmetic follow-up to v1.4.0.

The password generator button, both Copy password buttons, the generator's Password type and the field-level Generate… shortcut wear a key icon again. v1.4.0 replaced all five with the refresh icon — a bulk glyph edit meant to correct one new button rewrote every other use of the same codepoint.

No button changed what it does, and nothing else in v1.4.0 is affected. The icons are now pinned per button by test rather than by count, since a count moves with exactly this kind of mistake.

Full changelog: v1.4.0...v1.4.1

v1.4.0 — two-step and new-device logins

Choose a tag to compare

@Elevate08 Elevate08 released this 31 Aug 01:23
c9c3522

Logging in works again for accounts with a second factor. Eight defects stood
between such an account and a login that finishes; they compound, so each was
hidden behind the last.

Two-step login asks which method you use

The panel never sent --method, and a code without its method is a code the
server rejects. bw only puts the two-step token on the wire when a provider
came with it, so --code alone made the request a bare password grant — and an
email provider answers that by issuing a fresh code, invalidating the one
you just typed. Authenticator codes survived the omission because the server
does not issue them; emailed ones never could.

An account with more than one method fared worse still: the login failed with
Login failed. No provider selected. and no way forward.

So the method is now asked once per account, before any code is collected, and
remembered per login address. Choosing Email is also what makes Bitwarden
send the email. Change method on the code screen asks again.

A login waiting on an emailed code survives the panel closing

It could not before — closing dropped the master password and the login stage,
so going to read the code meant coming back to a blank form. An emailed code
cannot be read without leaving the panel, which made email two-step and
new-device verification impossible to complete here at all.

The login is now held for five minutes, on the wall clock so a suspend counts
against it, and reopening lands on the field that was waiting.

New-device verification (#4)

A login on a machine Bitwarden has not seen before used to ask for the emailed
code over and over: bw login has no flag for that code, and the step that
needs it never reads --code. The panel now answers bw's prompt directly, and
offers a terminal only if the login meets something it cannot answer.

Fixed

  • A bw status check no longer cancels the login it lands in the middle of. It
    takes seconds and answers about the world as it was when it started, so it
    reported unauthenticated and the panel killed the login you had just
    submitted — the button dropped out of "Verifying…" and nothing was shown.
  • A verification code typed into the panel is no longer discarded on the way
    out. Typing into a field breaks the binding to the state behind it, so
    clearing that state left the field showing a code the login could not see.
  • A login no longer has to be submitted twice, from either of two causes: a
    submit queued against the output-buffer scrub was dropped, and a password
    delivery that missed its window left the button for you to press again.
  • A vault that has never synced is no longer shown as an empty vault. bw login
    swallows a failed sync and still prints a working session, onto a local vault
    holding no ciphers.
  • A login that ends without a session says so, instead of failing silently.
  • An account whose only two-step methods are ones the CLI cannot perform — a
    passkey, or Duo — says so and points at API key login.

Security

  • Logging out no longer takes the cursor out of the master password field a few
    seconds later, which moved the rest of the password into the unmasked email
    field. The same fix covers the API key form.
  • The one login that runs with bw's prompts enabled answers on a pipe rather
    than a pty, so an unexpected prompt still ends the login instead of hanging it
    with the master password loaded.
  • A closed panel holds one thing it did not before: a login stopped on a second
    factor keeps the master password and its stage for five minutes. A deliberate
    exception, bounded on the wall clock, ended early by locking, logging out, or
    succeeding.

Upgrading

Nothing to do. The remembered method is absent until a login writes it, so your
first login after upgrading behaves as it did before and remembers your method
on the way through.

Every login result is now logged with the branch it took, the exit code and byte
counts — lengths and flags only, never a session, never a code. Read it with
quickshell log -f | grep qs-bitwarden.

Full changelog: v1.3.1...v1.4.0

v1.3.1

Choose a tag to compare

@Elevate08 Elevate08 released this 26 Aug 14:35
3b1348c

Fixed

  • Fixes #2: Email/password login now asks for credentials first and reveals the verification-code stage only when Bitwarden requires it.
  • Recognizes the standalone "Code is required." challenge emitted by Bitwarden CLI 2026.2.0.
  • Replaces the credential fields with a focused verification prompt and provides a Back to credentials action.

Full Changelog: v1.3.0...v1.3.1

v1.3.0: Faster Unlocks & Hardened Secret Handling

Choose a tag to compare

@Elevate08 Elevate08 released this 24 Aug 14:48

Release v1.3.0 makes locked-vault unlocks and logged-out sign-ins substantially faster while tightening every transient-secret boundary.

Highlights:

  • Prewarms authentication through private runtime FIFOs without putting credentials in command arguments.
  • Renders vault items before deferred folder, organization, and status metadata.
  • Adds deterministic performance coverage from 100 to 5,000 items.
  • Scrubs process collectors and cancels attachment and generator subprocesses safely.
  • Verifies session, PIN, and fingerprint credentials are absent from the OS keyring before another login.
  • Refreshes screenshots and the Bitwarden Vault Plugin preview.

See CHANGELOG.md for the complete release notes.

v1.2.1: Security & Hardening

Choose a tag to compare

@Elevate08 Elevate08 released this 23 Aug 21:54

Release v1.2.1: Process memory zeroing, suspend inhibitor, screen-lock triggers, and boundary validations.

v1.2.0

Choose a tag to compare

@Elevate08 Elevate08 released this 23 Aug 13:26

Five security review passes over the whole plugin since 1.1.1, each starting
cold with no memory of the last. Thirteen findings, all fixed, all with
regression tests. Nothing here changes how the plugin is used.

The vault stays shut

A suspended machine now counts against the auto-lock. Qt schedules its
timers on the monotonic clock, which Linux stops while the machine is
suspended, so a fifteen-minute countdown armed just before the lid closed still
had fifteen minutes left when the lid opened -- a vault left overnight came
back exactly as open as it was left. The deadline is now kept in wall-clock
terms as well and polled, and between the two it is whichever notices first
that locks.

A vault read that lands after the lock is discarded. Nothing cancels a bw
that is already running, and bw list items takes seconds on a large vault. A
lock or a logout could be followed a moment later by the whole item list
arriving and settling back into a panel that had just thrown it away -- every
login's password rides along in its raw object. After logging in to a second
account, the first account's items were what the list drew, and what Enter
copied, until the new account's items landed. Every reader now records the
vault generation it started under, and an answer from a generation that has
passed is dropped.

Logging out takes the keyring with it. Two of the three entries the plugin
writes are the master password -- as it is for fingerprint unlock, encrypted
under your PIN for PIN unlock -- and both live in the login keyring, which is a
file on disk. The PIN copy was never cleared on logout, and the fingerprint
copy only when the panel happened to believe a reader was present. Both are
cleared unconditionally now, as is the session token when the vault locks.

Locking clears what the panel is holding. A generated password nobody
copied, an item or Send left mid-compose, the payload on its way to bw, and
the master password typed into either setup form all used to outlive a lock in
a process that runs for the whole desktop session.

Nothing outside is trusted

A hostile server cannot exhaust or mislead the shell. A forty-byte reply
claiming an item had two hundred million attachments was enough to take the
whole Quickshell process down, bar included; list lengths are bounded now. An
attachment size reported in exponential notation silently disabled both the
download size limit and the free-space check, because the shell reads 1e+30
as an error rather than a number -- sizes are clamped before they reach the
script. And an id the server chooses can no longer arrive at bw as an option:
-- ends the options first.

A web page cannot freeze the bar. The scanner that reads a window title
looking for a site name backtracked quadratically, so a long enough title --
and a page writes its own title -- cost seconds of the GUI thread every time
the panel opened over that tab. It reads the same titles the same way in one
linear pass now. Learned suggestions are also bounded: the store only ever
grew, and the first read past its cap wiped everything you had taught it.

The generator's server is trusted only while it is ours. bw serve has no
authentication and a loopback port is reachable by every account on the
machine, so an HTTP 200 was never evidence the answer came from us. The port is
probed before we start, anything already answering means bw generate carries
that visit instead, and a value already delivered is dropped if our own server
turns out not to have bound. The server also now lives only as long as the
generator screen rather than the whole session.

Settings cannot fail open

Nothing validates shell.json, and a bad value there failed quietly in the
dangerous direction: a non-numeric minute count reached QML as NaN, landed in
an integer property as 0, and 0 is how "never lock" is spelled, while a
count past the documented ceiling overflowed the timer's interval into a
negative number that never fired. Each numeric setting is now held to its
documented range on the way in, and anything unreadable falls back to its
default rather than to zero.

v1.1.1

Choose a tag to compare

@Elevate08 Elevate08 released this 22 Aug 19:21

A security fix to the attachment saver, from the marketplace listing review.

An attachment is placed without following what is already there

The saver picked its path with [ -e "$out" ] and then asked bw to write that
path. Neither half is safe on its own. -e follows symlinks, so a dangling
link sitting in the download folder read as a free name and the write created
the file the link pointed at instead. And a test that is true when it returns
says nothing about the moment after it returns, so a link dropped in between
the check and the write still won.

The bytes now land in a freshly made private directory inside the destination
folder, and the finished file claims its name with link(). That call never
follows the last component of the new path and fails outright if anything is
already there, so the existence test and the creation are a single operation
with no window between them: a symlink is stepped around rather than followed,
and an existing file still cannot be overwritten -- " (1)", " (2)" and so on go
before the extension until the name is free. Filesystems with no hard links
(FAT, exFAT, most phone mounts) have no symlinks either, so on those the
fallback is a plain no-clobber move.

Attachment downloads are bounded

Retrieval had no ceiling of any kind. The size the vault reports is the
server's word rather than proof, so it only buys an early and readable
refusal; the limits that hold when it lies are RLIMIT_FSIZE on what can be
written (512 MB, Bitwarden's own per-file ceiling), a 15 minute timeout on a
transfer that never ends, and a free-space check that keeps a download from
taking the last of the disk. Whichever limit trips, the staging directory goes
with it and nothing partial is left behind.

The decrypted file is no longer world-readable either: the saver runs under
umask 077, so the attachment is 0600 in staging and 0600 where it lands.

v1.1.0

Choose a tag to compare

@Elevate08 Elevate08 released this 22 Aug 18:44

Attachments, and the stream limits raised in the marketplace listing review.

Every stream the shell buffers is capped at the producer

The panel is part of a long-lived shell process, and it collected complete
command output into memory: the item list, item detail, folders, collections,
organizations, Sends, the session handoff file, the learned-associations file
and diagnostic stderr. Nothing bounded any of them, so a large vault item or a
large list forced an unbounded allocation inside the shell before any
parse-time limit could help.

Quickshell's StdioCollector offers no size limit of its own, so each stream is
now capped where it is produced -- head -c in the command itself -- and the
data never reaches QML at all. Caps are sized to the stream: 16 MB for the item
list, 8 MB for Sends, 4 MB for a single item, 2 MB for folders, collections and
organizations, 64 KB for status and command responses, 8 KB for diagnostic
stderr, 4 KB for session tokens and the handoff file.

A cap must not hide an error. A pipeline exits with the status of its last
command, which is the cap, so set -o pipefail restores the producing command's
status; the one status not forwarded is the SIGPIPE the cap itself delivers when
it truncates an oversized but otherwise healthy stream. Failures are reported as
failures and truncation is not mistaken for one.

Item attachments

Item detail lists the attachments on a login, with their size, and downloads one
to the XDG download directory. The name is sanitised before it becomes a path
and an existing file is never overwritten -- a second download lands beside the
first.