Releases: DrewCarlson/bwx-cli
Releases · DrewCarlson/bwx-cli
v2.3.1
Immutable
release. Only release title and notes can be modified.
[2.3.1] - 2026-04-27
- Fix
bwx touchid enrollfailing withSecItemAdd: status -34018
on signed builds. 2.3.0 moved Touch ID wrapper-key storage to the
data-protection keychain, but the Developer-ID signing path didn't
carry the entitlements that scope binaries into a DP keychain
group, so everySecItem*call returnederrSecMissingEntitlement.
scripts/sign-macos.shnow embedsapplication-identifierand
keychain-access-groups = ["TEAMID.bwx"]on the
hardened-runtime path (Team ID extracted from the identity string,
override withTEAM_ID=…). At runtimesrc/touchid/keychain.rs
checks its ownapplication-identifierentitlement via
SecTaskCopyValueForEntitlementand only requests the DP keychain
when present; cargo-install, ad-hoc,Apple Development, and
unsigned local builds transparently fall back to the legacy file
keychain (which works without entitlements).
v2.3.0
Immutable
release. Only release title and notes can be modified.
[2.3.0] - 2026-04-27
- Faster
--folderlookup inbwx add/generate. When passeing
--folder <name>, the CLI used to decrypt every folder name one
IPC at a time just to check whether the requested folder already
existed. Folder lookup now batches into a singleDecryptBatch,
one IPC regardless of folder count. - Faster
bwx add/generate/edit. NewEncryptBatchIPC
(mirrorsDecryptBatch): the agent encrypts a vector of
plaintexts in one shot and returns per-item results.bwx add
andbwx generatepreviously fired one IPC each for name,
username, password, notes, and every URI;bwx editfired one
per modified field. All three now stage their plaintexts through
a localEncryptBatcherhelper and flush a single round trip.
Minor protocol bump — the new variants are additive, so older
agents still work for any command that doesn't actually use
EncryptBatch, butadd/generate/editrequire both binaries
on this version. - Faster full-detail decrypt for
bwx get --full/history/code.
The full-cipher decrypt path (decrypt_cipher/
decrypt_cipher_using_search) previously fired one IPC per
cipherstring — folder, notes, every history password, both halves
of every custom field, and every variant-specific field (login
password/totp/URIs, identity address fields, card numbers, SSH
key parts). An entry with several history rows and custom fields
could rack up 20+ synchronous round trips. Both functions now
stage every field into oneDecryptBatchvia a sharedBatcher
helper and assemble results from the response. - Faster
bwx get/code/edit/remove/history/searchon
large vaults.find_entryandsearchpreviously made one IPC
round-trip per cipherstring per entry (name + username + folder +
notes + every URI + every custom field), which scaled as
O(entries × fields) and could easily reach hundreds of round-trips
on a sync. Both now route through a newdecrypt_search_ciphers
helper that bundles every field into a singleDecryptBatchIPC,
with the agent decrypting them in one shot. - Configurable diagnostic logging. New
loggingconfig key
(bwx config set logging on|off, defaulton) toggles a single
bucket of diagnostic output written to stderr — error/warning
messages plus debug records used to trace internal operations and
time long-running steps. Whenoff,bwxandbwx-agentemit
nothing on stderr. Output is restricted to our crates so
third-party libraries don't leak through;RUST_LOG, when set,
overrides the configured level for ad-hoc debugging. The new
bwx::debug_time!macro is zero-cost when logging is off (no
Instant, no string formatting). - macOS-only commands no longer appear in
--helpon other
platforms.bwx touchid {enroll,disable,status},bwx setup-macos,
andbwx teardown-macosare now#[cfg(target_os = "macos")]-gated
in the clapOptenum, so Linux/BSD builds simply don't list them
rather than listing them with a stub that errors at runtime. The
"(macOS only)" suffix has been dropped from the surviving help
text since it's now redundant. - Touch ID wrapper key now lives in the data-protection keychain.
AllSecItem*calls insrc/touchid/keychain.rspass
kSecUseDataProtectionKeychain = true. Items are scoped by the
binary's team-identifier rather than via per-binary login-keychain
ACLs, so reinstalling or upgrading a Developer-ID-signed bwx no
longer triggers the "bwx wants to access the keychain" prompt and
there is no "Always Allow" ACL to manage.
v2.2.1
Immutable
release. Only release title and notes can be modified.
[2.2.1] - 2026-04-26
- Same-team peer verification on the agent socket (macOS). When
the agent itself is signed with a Team Identifier (Developer ID or
Apple Development), it now requires connecting clients to be signed
by the same team via aSecCodeCheckValidityrequirement string.
Closes the "another process running as my uid that's signed by some
other identity". Ad-hoc and unsigned agent builds (local
cargo install, forks without a paid Apple cert) keep the prior
same-uid-only behavior, so dev workflows aren't disrupted. - Rejected agent connections now send a
Response::Errorbefore
closing instead of dropping silently, so the CLI fails fast with a
real error message.bwx stop-agent(and any other path that ends
up sendingQuit) now bounds itswait_for_exitpoll at 2 seconds
rather than waiting forever. - Fix macOS releases getting killed at exec time. 2.2.0 binaries
were signed with akeychain-access-groupsentitlement that AMFI
treats as restricted; without a provisioning profile (which a bare
CLI Mach-O can't carry) the kernel rejects the signature and SIGKILLs
the process beforemainruns. Drop the entitlement; Touch ID
enforcement stays put — the agent's presence check still fires
before the wrapper key is released, the only loss is the
Keychain-side biometric ACL which was never actually being applied
on shipped builds anyway. bwx config show/set/unsetnow offer the valid configuration keys
via shell completions and reject unknown keys at the CLI parse step.
bwx config unset sync_intervalresets it to the default instead of
erroring.
v2.2.0
Immutable
release. Only release title and notes can be modified.
[2.2.0] - 2026-04-26
- Cache
Config::load()once perbwxinvocation; trims a few ms of
redundant disk + JSON parse from every command that touches the
vault. - Reuse the search-cipher plaintext when finalising the matched entry
infind_entry, soget/code/execskip the redundant
decrypt-via-IPC of name, folder, notes, and login username. - Skip the second KDF run during
bwx login. The agent now reuses the
Identityproduced while authenticating to unlock the vault,
shaving 100-500ms off Argon2id-backed accounts on first login. - Cache the agent protocol-version probe within a single CLI
invocation, so commands that re-enterensure_agent(e.g.
--clipboard) don't pay for a redundant IPC round-trip.
stop_agent()now invalidates that cache so a later
ensure_agentre-verifies a fresh agent. - Memoise compiled URI-match regexes during entry lookup. The find
loop runs the same patterns against every cipher several times per
search; compiling once per pattern keeps regex-heavybwx get
callers off the hot path. bwx listnow decrypts the whole vault in a singleDecryptBatch
IPC instead of one round-trip per field. AddsAction::DecryptBatch
andResponse::DecryptBatchto the agent protocol; per-item failures
are reported back to the caller without aborting the batch.- Reuse a single
UnixStreamfor every IPC inside abwxinvocation
instead of opening a fresh connection per action. Cached socket is
cleared on send/recv failure (transparent reconnect) and onQuit. - Cap
DecryptBatchrequests at 10,000 items and forward only the
top-level error context for per-item failures, so the agent doesn't
echo wrapped error chains over IPC. - Release workflow attestation step migrated from
actions/attest-build-provenancetoactions/attest@v4.1.0(the
upstream-recommended target for new implementations; the old action
is now just a passthrough wrapper). - Wire-format change. CLI ↔ agent IPC moves from line-delimited
JSON to length-prefixed MessagePack (rmp-serde). 4-byte big-endian
payload size, 16 MiB cap on both directions, sameRequest/
Responseenum shapes. Smaller and faster to encode/decode for
large payloads (bwx list,DecryptBatch). Runbwx stop-agent
after upgrading so the new CLI doesn't try to talk to a still-
running pre-2.1.1 agent. - Agent now caps each request read at 30 seconds and exits the
connection cleanly on timeout, so a peer that sends a length prefix
and then stalls can't pin a tokio task forever.
v2.1.0
Immutable
release. Only release title and notes can be modified.
[2.1.0] - 2026-04-26
Added
bwx exec --env VAR=ENTRY[#FIELD] -- <cmd>. Run a child process
with vault fields bound to environment variables. Each--envflag
resolves a vault entry (by name, UUID, or URI) and an optional field
(defaults topassword); the value is passed viaexecve()only and
the parent zeroizes its in-process copy as soon as the child has been
spawned, so secrets never touch disk. The exec'd child's exit code
(or128 + signalon Unix signal termination) is propagated. Drop-in
replacement fordirenv+ plaintext.envfiles for CLI workflows
(bwx exec --env DB_URL=db/prod#uri -- terraform apply).
v2.0.2
Immutable
release. Only release title and notes can be modified.
[2.0.2] - 2026-04-25
Project renamed from rbw to bwx-cli. The binaries are now bwx
and bwx-agent; XDG dirs move from ~/.config/rbw etc. to
~/.config/bwx; the macOS Keychain service is "bwx"; LaunchAgent
labels are drews.website.bwx.*; env vars use a BWX_ prefix
(BWX_PROFILE, BWX_TTY, …). Existing installs need to migrate
config/cache dirs and re-enroll Touch ID.
First-class macOS support, an extensive security hardening pass, and a
substantial dependency-tree reduction. Linux/BSD users get a drop-in
replacement for upstream rbw with the same hardening applied.
Added
- Touch ID unlock (macOS).
bwx touchid enroll/disable/statuswrap
the vault keys under a Keychain-held wrapper key so biometry can replace
the master password. Three signing tiers (Developer ID, Apple Development,
ad-hoc) are detected at runtime and use the strongest available ACL. - Per-operation Touch ID gate.
touchid_gate = off | signing | all
optionally requires a biometric prompt before each vault read or SSH
sign, coalesced to one prompt perbwx <command>via a session token. - Native macOS dialogs. Master password, 2FA codes, and SSH-sign
confirmation render as Aqua dialogs viaosascriptwhen pinentry isn't
available; toggled bymacos_unlock_dialog. bwx setup-macos/bwx teardown-macos. Install/remove two
LaunchAgents — one forbwx-agentkeepalive, one to publish
SSH_AUTH_SOCKinto launchd's environment so GUI apps inherit it.- Built-in SSH agent + git commit signing. Serves SSH keys stored as
Bitwarden "SSH Key" items.bwx ssh-public-key,bwx ssh-allowed-signers,
andbwx ssh-socketsupportgpg.format = sshworkflows. ssh_confirm_sign. Optional pinentry CONFIRM dialog before each SSH
signature, with the requesting program's name + PID surfaced in the
prompt so the user knows which client is asking.- End-to-end test suite. A vaultwarden-spawning harness with 43
scenarios, run on Ubuntu and macOS in CI.
Changed
- Replaced 20+ third-party crates with hand-rolled equivalents where the
upstream surface was overkill for our use:axum→ raw tokio HTTP
parser for the SSO callback,region→ directrustixmlock/munlock
(also fixes the muslmunlockpanic onRLIMIT_MEMLOCK-constrained
CI runners),humantime,textwrap,terminal_size,is-terminal,
percent-encoding,uuid,directories,libc(direct usage),
serde_path_to_error,block-padding,arrayvec,serde_repr,
totp-rs,base32,anyhow,daemonize,env_logger,hkdf,
clap_complete_nushell/clap_complete_fig,futures-channel,
futures.openis now gated behind the optionalsso-browser
feature. - Tokio feature set narrowed from
"full"to the actually-used subset.
Security
- All on-disk state forced to 0o600 / 0o700.
config.json,db.json,
touchid.json,device_id, the pidfile, and the agent socket dir are
re-tightened on every write — explicitset_permissionsin addition to
OpenOptions::mode, since the latter only applies on file creation. - Wrapper seed kept in
locked::Vecend-to-end. Touch ID enroll +
unlock paths never materialize the 64-byte seed in plain heap or on
the stack;keychain::loadreturnslocked::Vecdirectly. - Pinentry stdout zeroized. osascript / Assuan output buffers are
scrubbed before the parentOutputdrops, so a typed master password
doesn't linger in the heap. - SSH key plaintext minimized.
ssh-agentSign handler decrypts the
private key only after Touch ID + pinentry CONFIRM succeed; cancel
paths leave no key material in memory. - SSO callback hardened. State-mismatch error no longer embeds the
64-char OAuth state token. Raw tokio listener replaces axum (smaller
attack surface, no untrusted body parsing). - Agent IPC hardened. Per-connection
getpeereid/SO_PEERCRED
check rejects cross-uid clients on shared hosts. Both control and
ssh-agent sockets bind atomically (tmp + rename) instead of unlink +
bind. Request and response framing capped at 16 MiB. - MAC verify made explicit. CipherString verification now uses
hmac::Mac::verify_slice(constant-time viasubtle::ConstantTimeEq)
with a comment documenting the guarantee. - Touch ID session bookkeeping fixed. Sessions are recorded only
after a successful unlock, not on bare presence, so a failed unwrap
doesn't leave a usable auth window for the TTL. - SIGTERM/SIGINT zeroize agent state. In-memory keys are dropped
through theirmlock/zeroizeDrop impls before exit, instead of
living in kernel buffers until reaping. pwgenreturnslocked::Password. Generated passwords are
mlocked + zeroized on drop in the immediate caller's scope.
Install
- macOS:
git clone … && ./scripts/install.sh && bwx setup-macos. The
install script auto-picks the strongest signing identity available
(Developer ID > Apple Development > ad-hoc) and applies the matching
Keychain ACL strategy.