Tools/pm 39238/complete file send lifecycle - #1303
Conversation
…ub.com:bitwarden/sdk-internal into tools/pm-34719/add-send-commands-to-rust-cli-2
…ub.com:bitwarden/sdk-internal into tools/pm-34719/add-send-commands-to-rust-cli-2
Co-authored-by: John Harrington <84741727+harr1424@users.noreply.github.com>
Co-authored-by: John Harrington <84741727+harr1424@users.noreply.github.com>
- Trim heavy doc-comments on private items in bitwarden-send/edit.rs - Rename `--output-file` to `--output` on `bw send get` (PR #1178 freed the long form) - Replace `PM-34719` TODO references with follow-up tickets (PM-39238 file-send lifecycle, PM-39239 access URLs, PM-39240 encodedJson) - Delete stale `--deleteInDays` validation TODO (now enforced at clap level via `value_parser`) - Soften text/file disambiguation TODO to a behavior note (matches legacy CLI) - Update `bw send receive` stub to reference sibling ticket PM-34718 - Minor wording fix in tests/send.rs
Replace "type-immutability enforcement" with an explicit "reject text↔file type changes" so readers don't confuse it with Rust's `mut`.
- Move SendArgs, SendCommands, and the 8 Send*Args structs from
tools/mod.rs to tools/send.rs so the arg definitions live alongside
their `impl BwCommand for ...` blocks. `pub use send::SendArgs`
preserves the `tools::SendArgs` external import path.
- Replace the custom `parse_delete_in_days` Result-returning parser with
a `PossibleValuesParser`-based combinator. clap now surfaces the
allowed values in `--help` output:
[possible values: 1, 2, 3, 7, 14, 30]
- Update the matching integration test to assert on clap's "possible
values" error wording.
clippy::implied_bounds_in_impls flagged the `+ Clone` as redundant — `TypedValueParser`'s supertrait already requires `Clone`.
bw send get --text and the default output of bw send create now emit a shareable access URL (<web-vault>/#/send/<access_id>/<url_b64_key>) instead of the raw access_id. The web-vault base is derived from the client's configured API URL, so self-hosted and cloud both work without hardcoding bitwarden.com.
Implements bw send create --file: encrypt the local file under the send key and upload via SendClient::upload_send_file, which now dispatches Direct (multipart POST) vs Azure (single-blob PUT to the pre-signed URL, renew-on-expiry). Adds a premium precondition check (reads the premium claim from the access-token JWT) before file create, and adds premium to bitwarden_core::auth::JwtToken. Verifies text/file disambiguation and re-points the get --output stub to PM-34718.
Address code review: send the encrypted buffer length as SendRequestModel.file_length (matching legacy's encrypt-then-create ordering) instead of None, and stop setting SendFileModel.size to the plaintext length on create (leave None; the server derives it from the uploaded blob). create_file_send now takes the plaintext file_buffer, encrypts it internally under the recorded send key, sets file_length, and returns the ciphertext for upload.
…te-file-send-lifecycle # Conflicts: # crates/bw/src/tools/send.rs # crates/bw/tests/send.rs
| ) -> color_eyre::eyre::Result<bitwarden_send::SendView> { | ||
| // Read the plaintext before creating the send so a read failure aborts before we register a | ||
| // send that would then have no content. | ||
| let bytes = std::fs::read(path).wrap_err_with(|| format!("Could not read file {path}"))?; |
There was a problem hiding this comment.
Path traversal attack possible - critical severity
The application constructs file paths using untrusted data, potentially leading to a path traversal vulnerability. An attacker could manipulate these inputs to access, create, or overwrite sensitive files.
Show fix
| let bytes = std::fs::read(path).wrap_err_with(|| format!("Could not read file {path}"))?; | |
| // Prevent path traversal attacks by rejecting paths containing '..'. | |
| let path_buf = std::path::Path::new(path); | |
| if path_buf.components().any(|c| c == std::path::Component::ParentDir) { | |
| return Err(eyre!("Invalid input: {}", path_buf.display())); | |
| } | |
| let bytes = std::fs::read(path_buf).wrap_err_with(|| format!("Could not read file {path}"))?; |
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info
There was a problem hiding this comment.
@AikidoSec ignore: local CLI argument, not a trust-boundary crossing — the operating user already has filesystem access to any path they pass here.
There was a problem hiding this comment.
✅ Based on your feedback, we ignored this issue because of the following reason:
local CLI argument, not a trust-boundary crossing — the operating user already has filesystem access to any path they pass here.
🔍 SDK Breaking Change DetectionSDK Version:
Breaking change detection uses the build of the SDK from this branch, including any incompatibities pre-existing on or merged into this branch. Check the workflow logs to confirm. |
dani-garcia
left a comment
There was a problem hiding this comment.
Looks pretty good, but some of the comments are pretty wordy and unneeded. It would be good to trim them down.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1303 +/- ##
==========================================
+ Coverage 85.90% 86.06% +0.16%
==========================================
Files 493 495 +2
Lines 71347 72535 +1188
==========================================
+ Hits 61290 62429 +1139
- Misses 10057 10106 +49 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Addresses PR feedback flagging duplicate/unnecessary comments.
harr1424
left a comment
There was a problem hiding this comment.
This looks good, I agree on the above regarding some of these comments, though.
…com:bitwarden/sdk-internal into tools/pm-39238/complete-file-send-lifecycle
| ) -> color_eyre::eyre::Result<bitwarden_send::SendView> { | ||
| // Read the plaintext before creating the send so a read failure aborts before we register a | ||
| // send that would then have no content. | ||
| let bytes = std::fs::read(path).wrap_err_with(|| format!("Could not read file {path}"))?; |
There was a problem hiding this comment.
Path traversal attack possible - critical severity
The application constructs file paths using untrusted data, potentially leading to a path traversal vulnerability. An attacker could manipulate these inputs to access, create, or overwrite sensitive files.
Show fix
| let bytes = std::fs::read(path).wrap_err_with(|| format!("Could not read file {path}"))?; | |
| // Prevent path traversal attacks by rejecting paths containing '..'. | |
| let path_buf = std::path::Path::new(path); | |
| if path_buf.components().any(|c| c == std::path::Component::ParentDir) { | |
| return Err(eyre!("Invalid input: {}", path_buf.display())); | |
| } | |
| let bytes = std::fs::read(path).wrap_err_with(|| format!("Could not read file {path}"))?; |
Reply @AikidoSec ignore: [REASON] to ignore this issue.
More info
There was a problem hiding this comment.
@AikidoSec ignore: local CLI argument, not a trust-boundary crossing — the operating user already has filesystem access to any path they pass here.
There was a problem hiding this comment.
✅ Based on your feedback, we ignored this issue because of the following reason:
local CLI argument, not a trust-boundary crossing — the operating user already has filesystem access to any path they pass here.
There was a problem hiding this comment.
@adudek-bw yes, the user has access to the filesystem, but if the path contains "../../../" for example it could still write where they're not expecting it to be. This should still be fixed.
harr1424
left a comment
There was a problem hiding this comment.
The comments seem much more focused now! I found some error output which production users might find strange; should we consider referencing the internal Jira tickets using code comments as opposed to command output?
| // be a worse UX than an explicit "not implemented" error. | ||
| Some(SendCommands::Get(args)) if args.output_path.is_some() => Err(eyre!( | ||
| "`--output` on `bw send get` is not yet implemented (tracked under PM-39238)." | ||
| "`--output` on `bw send get` is not yet implemented (tracked under PM-34718)." |
There was a problem hiding this comment.
❓ Will this output show in our production release? If so, does it make sense to reference an internal Jira ticket in the output itself?
There was a problem hiding this comment.
❓ Will this output show in our production release? If so, does it make sense to reference an internal Jira ticket in the output itself?
There was a problem hiding this comment.
❓ Will this output show in our production release? If so, does it make sense to reference an internal Jira ticket in the output itself?
Adds SendAccessKey (bitwarden-send), the anonymous-decrypt primitive that derives a Send's symmetric key from the URL fragment without any account key store or logged-in user. Wires up bw receive and its bw send receive alias: URL/API/identity resolution, password resolution (--password/--passwordenv/--passwordfile with interactive fallback gated on BW_NOINTERACTION), the password and email-OTP send-access token negotiation flow, and file download/decrypt/save via a new --output flag (replacing the previous --obj path flag to match bw send get's existing convention). Adds --fullObject for dumping the decrypted Send as JSON. Leaves bw send get --output as an explicit not-yet-implemented error with an expanded comment, since no owner-scoped download endpoint exists server-side.
…com:bitwarden/sdk-internal into tools/pm-39238/complete-file-send-lifecycle
# Conflicts: # crates/bw/src/tools/send.rs
SendFileTemplate derived Default, which gave send_type: 0 (the text discriminant) since that's u8::default() — the derive silently ignored the "1 = file" comment. bw send template send.file emitted type: 0 instead of 1. Replaced the derive with a manual Default impl and added a regression test pinning the discriminant.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-39238
📔 Objective
Completes the file-send lifecycle for the Rust CLI bw send, which PR #1176 (PM-34719) stubbed out with "not yet implemented" errors.
🚨 Breaking Changes
Changes to the WASM/UniFFI binding surface, safe today (no live consumers — the clients repo routes file-send creation to the legacy service and does not call these):
The cross-repo TypeScript compatibility check will confirm no client breakage.