Add Bitwarden support and stop passing passwords on the command line - #4
Open
FilipHert wants to merge 3 commits into
Open
Add Bitwarden support and stop passing passwords on the command line#4FilipHert wants to merge 3 commits into
FilipHert wants to merge 3 commits into
Conversation
Connection passwords can now be stored as bw://<item-id> references and are resolved through the Bitwarden CLI when a connection is launched. This works with bitwarden.com, self-hosted Bitwarden and Vaultwarden. Bitwarden has no library interface, so the provider runs "bw serve" as a hidden child process bound to 127.0.0.1 on a random free port and talks to its local REST API. The server starts lazily on first use and is stopped when the application exits; on Windows it is placed in a kill-on-close job object and on Linux it gets a parent death signal, so a crash cannot leave an orphaned server holding an unlocked vault. The child inherits BW_SESSION from the environment, so the vault is unlocked once in the user's shell. MremoteGO never asks for, sees or stores the master password. To avoid wiring a second password manager directly into the config manager, launcher and GUI, secret handling now goes through a small Provider interface and a process-wide Registry. 1Password keeps working unchanged; the crypto package uses the registry to decide that references must not be encrypted at rest. The connection dialogs gain a "Bitwarden..." button that lists and searches vault login items and writes back a reference, and a "Store password in Bitwarden" option that creates a login item from a typed password. The startup authentication warning is now generic across providers, only asks about providers the config actually uses, and runs off the UI goroutine because starting the CLI takes a moment. Covered by unit tests that exercise the client against a fake bw serve, so neither the CLI nor a GUI is required to run them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On every platform the connection password was visible in the process list for the lifetime of the client process, which means any local user could read it: putty.exe -ssh ... -pw <password> host sshpass -p <password> ssh ... cmdkey /generic:TERMSRV/host /user:u /pass:<password> PuTTY itself documents -pw as insecure and recommends -pwfile, so the password now goes into a private temporary file that PuTTY reads while parsing its arguments; the file is removed as soon as the process is up, or after a short grace period. Files left behind by a killed run are cleaned up at start-up. sshpass cannot take the password from the environment directly here, because the terminal emulator hop (gnome-terminal, Terminal.app) does not forward environment variables or file descriptors. The generated shell snippet therefore reads the file into SSHPASS and deletes it before ssh is executed, so the password is neither in argv nor on disk for longer than necessary. RDP credentials are written through the Windows Credential Manager API instead of shelling out to cmdkey. The blob is encoded as UTF-16LE, which is what mstsc expects; UTF-8 would store a credential that looks valid but silently fails to log in. launchInTerminal now takes the prepared shell snippet plus an equivalent argument vector, used only on systems with no terminal emulator, and the host name is passed explicitly rather than guessed from the arguments. Every value interpolated into a snippet is single-quote escaped, as before. Known limitation: xfreerdp on Linux is still invoked with /p:. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Bitwarden CLI refuses to start "bw serve" at all when no user is
logged in: it prints "You are not logged in." on stderr and exits. That
surfaced as "bw serve exited before it became ready", which tells the
user nothing about what to do.
The start-up failure output is now classified, so a missing login
reports ErrNotAuthenticated and a locked vault reports ErrVaultLocked,
the same errors the running server would produce. Connecting with an
unusable vault now says "not logged in to bitwarden" and the start-up
dialog offers the sign-in instructions.
Also adds two tests that run against the real system when it is
available and skip otherwise:
- the Bitwarden CLI, to check that the helper process starts, answers
and is stopped, treating an unauthenticated CLI as a valid outcome;
- cmdkey, to check that the credential written through the Credential
Manager API is byte for byte identical to what cmdkey stores. This
pins the UTF-16LE blob encoding and enterprise persistence that
mstsc relies on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Bitwarden as a second password manager alongside 1Password, and fixes the
way passwords are handed to the connection clients.
Connection passwords can now be stored as
bw://<item-id>references, resolvedthrough the Bitwarden CLI at connect time. This works with bitwarden.com,
self-hosted Bitwarden and Vaultwarden.
Why a provider abstraction
OnePasswordProviderwas referenced directly from the config manager, thelauncher and the GUI, and
op://was hard-coded in five places includingcrypto.ShouldEncrypt. Rather than duplicate all of that, secret handling nowgoes through a small
secrets.Providerinterface and a process-widesecrets.Registry. 1Password behaves exactly as before; adding a thirdprovider later is a single file plus one line in
Default().The registry is a singleton because
config.NewManager,launcher.NewLauncherand
MainWindow.openConfigeach construct providers, and Bitwarden owns ahelper process that must exist only once.
How Bitwarden is reached
Bitwarden has no library interface, so the provider runs
bw serveas a hiddenchild process bound to
127.0.0.1on a random free port and talks to its localREST API. It starts lazily on first use and is stopped on exit. On Windows the
child is placed in a kill-on-close job object; on Linux it gets a parent death
signal. That matters because
bw servehas no authentication of its own, so anorphaned server would leave an unlocked vault reachable on loopback.
The child inherits
BW_SESSION, so the user unlocks the vault once in theirshell. MremoteGO never asks for, sees or stores the master password. The
provider is written so a future
POST /unlockdialog would be a small addition.GUI
The add and edit connection dialogs share a new control group instead of
duplicating the 1Password block. It adds a Bitwarden... button that lists
and searches vault login items and writes back a reference, and a Store
password in Bitwarden option that creates a login item from a typed password.
The start-up authentication warning is now generic across providers, only asks
about providers the config actually references, and runs off the UI goroutine
because starting the CLI takes a second or two.
Security fix
Independently of Bitwarden, the connection password was visible in the process
list for the lifetime of the client process, so any local user could read it:
-pwfilewith a private temporary file, which PuTTY itselfrecommends over
-pw. The file is removed once the process is up, and filesleft by a killed run are cleaned at start-up.
sshpassgets the password throughSSHPASS. It cannot be set from Gobecause the terminal emulator hop does not forward the environment, so the
generated snippet reads the file into the variable and deletes it before
sshruns.UTF-16LE, which is what
mstscexpects; UTF-8 stores a credential that looksvalid but silently fails to log in.
Known limitation, left alone deliberately:
xfreerdpon Linux is still invokedwith
/p:.Testing
go test ./...was previously empty. This PR adds:bw servebuilt onhttptest, covering resolution, locked and unauthenticated vaults, itemcreation, list filtering and the absence of an
Originheader, which thereal server rejects;
crypto.ShouldEncryptand thepassword file handling;
Bitwarden CLI lifecycle, and a comparison proving the credential written
through the API is byte for byte what
cmdkeystores.A
go test ./...step was added to CI, and the Go version there was bumped from1.23 to 1.24 to match
go.mod.Verified manually on Windows 11: build with CGO, GUI starts,
bw://resolutionreports a clear error with no login, no
bw.exeleft behind after exit.Not addressed
ExtraArgsis still appended as a single argv element rather than split, whichpredates this change.
🤖 Generated with Claude Code