Expand description
bw receive / bw send receive — access a Bitwarden Send from a url.
This is the only Send flow that runs without a logged-in user: the send’s content is
decrypted with a key derived purely from the URL fragment
([bitwarden_send::SendAccessKey]), never from the account key store. Because the link can
point at any deployment, the command builds its own [PasswordManagerClient] per invocation
from URLs derived off the link itself (see resolve_urls) rather than reusing the session
client — a self-hosted receive link must not have its password hash sent to Bitwarden cloud
identity, and vice versa.
Both bw receive <url> and bw send receive <url> are the same command; their arg structs
are field-identical and both funnel into run_receive.
PM-40120: the legacy CLI’s version of this bug mints against its configured environment
but can leak the resulting real token to an attacker-controlled host on a follow-up fetch.
The decided fix (2026-08-17) is that a Send access token must never be requested from
anywhere but the server named in the Send link itself. resolve_urls already does this
unconditionally for both the API and identity origin, so the mint and fetch here always
target the same resolved host and a token can never cross the kind of domain boundary
PM-40120 tracked. A link naming an attacker-controlled host still sends that host a password
hash if the user proceeds — but per the decided fix, that’s the token’s only valid target,
not a leak to a second, different host.
Structs§
- Cloud
Region 🔒 - Receive
Inputs 🔒 - The flags
bw receiveandbw send receiveshare, normalized into one struct so the two entry points cannot drift apart.
Constants§
- CLOUD_
HOSTS 🔒 - The Bitwarden cloud deployments whose Send links do not carry their API host.
Functions§
- access_
with_ 🔒email_ otp - Email-OTP-protected Sends: the email request is what makes the server send the code, so the
expected outcome of the first call is an
email_and_otp_requirederror, not a token. - access_
with_ 🔒password - Password-protected Sends: resolve the password from flags/env/file/prompt, run it through the
same PBKDF2 recipe
bw send create --passwordused, and exchange it for a token. - attempt_
access 🔒 - Negotiate a send-access token, prompting for whatever credential the server says the Send
needs. Mirrors the legacy
attemptAccess: ask with no credentials first and branch on the typedsend_access_error_typethe server returns. - can_
interact 🔒 - Whether we may prompt the user.
- download_
bytes 🔒 - GET a pre-signed blob URL with the client’s shared HTTP stack (so proxy and TLS settings
apply), mirroring legacy’s
apiService.nativeFetch. - invalid_
grant_ 🔒type - Same idea as
invalid_request_type, but for theinvalid_grantresponse shape (used to detect an unknown Send id or an invalid password hash). - invalid_
request_ 🔒type - Extracts the typed
send_access_error_typefrom aninvalid_requestresponse, orNoneiferrisn’t that shape.attempt_accessandaccess_with_email_otpboth need to branch on this one sub-field of a deeply nested error enum; centralizing the match here keeps those call sites down to a singleSome(...) => ...comparison instead of repeating the full pattern. - parse_
send_ 🔒url - Extract
(send_id, url_b64_key)from the last two#-fragment segments. - password_
from_ 🔒args - Non-interactive half of
resolve_password, split out so the precedence is unit-testable. - prompt_
email 🔒 - prompt_
otp 🔒 - prompt_
password 🔒 - Prompt for the Send’s password.
inquirerenders to stderr, sobw receive <url> > out.txtstill captures only the Send’s content — the same reason legacy passesoutput: process.stderrto inquirer. - render_
access 🔒 - Fetch the Send with the negotiated token, decrypt it with the URL key, and render it.
- request_
token 🔒 - resolve_
password 🔒 - Resolve the Send’s password, prompting when it wasn’t supplied and the session is
interactive. Mirrors legacy’s
handlePasswordAuthprecedence exactly. - resolve_
urls 🔒 - Resolve the API and identity base URLs to talk to for a given Send link, plus whether that host is trusted.
- run_
receive 🔒 - Entry point for
super::ReceiveArgs’sBwCommandimpl, reached both from the top-levelbw receivecommand and fromSendCommands::Receive(bw send receive), which reuses the same arg struct rather than a hand-synced copy. - same_
origin 🔒 truewhenconfigureddenotes the same origin asorigin(already an ASCII origin serialization). Falls back to a string compare for values that aren’t parseable URLs, so a hand-editedconfig.jsonstill matches.- save_
file_ 🔒send - Download, decrypt, and save a file-type Send’s blob.
- token_
error 🔒 - Surface a token-negotiation failure we have no specific message for. The error’s
Debugcarries the server’serror_description, which is diagnostic and never contains send content or credentials. - trimmed 🔒