Skip to content

fix(crypto): tolerate unparseable EncStrings across FFI boundary - #1462

Open
quexten wants to merge 3 commits into
mainfrom
unparseable-encstring
Open

fix(crypto): tolerate unparseable EncStrings across FFI boundary#1462
quexten wants to merge 3 commits into
mainfrom
unparseable-encstring

Conversation

@quexten

@quexten quexten commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-42212

📔 Objective

A malformed EncString errored at parse time. In the WASM bindings that failure happened while
moving arguments across the FFI boundary, before the called method ran, so one bad item took down
an entire batch decryption instead of being reported as a per-item failure. This is a problem in vault decryption, because it expects to handle failures on a per item basis (see the test that's added).

FromStr now maps anything matching no known format to EncString::Unparseable, which keeps the
raw value so it round-trips unchanged. Decryption, unwrapping and to_buffer reject it with a
dedicated CryptoError::UnparseableEncString, so the failure surfaces where it can be attributed.
Call sites that must reject malformed input up front use the new EncString::parse_strict.

from_buffer (binary format) stays strict.

🚨 Breaking Changes

New EncString enum variant. Exhaustive matches on EncString in consumers need a new arm.

A malformed EncString used to error out at parse time, which for the
WASM bindings meant the failure surfaced while lifting arguments across
the FFI boundary, before the called method ran. A single bad item took
down an entire batch decryption instead of being reported as a per-item
failure.

FromStr now maps anything that matches no known format to
EncString::Unparseable, keeping the raw value so it round-trips
unchanged. Decryption, unwrapping and to_buffer reject it with a
dedicated CryptoError::UnparseableEncString, so the failure surfaces
where it can be attributed. Call sites that must reject malformed input
up front use the new EncString::parse_strict.
@quexten
quexten requested review from a team as code owners September 9, 2026 10:55
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude Code is reviewing this pull request...

If this comment does not update with results, check the Actions log.

@quexten quexten added bug Something isn't working t:fix Fix a bug labels Sep 9, 2026
@quexten
quexten requested review from dani-garcia and removed request for dereknance September 9, 2026 10:57
it("reports the malformed cipher as a failure and still decrypts the rest", async () => {
const { ciphers, valid, malformed } = await setup();

const result = await ciphers.decrypt_list_full_with_failures([valid, malformed]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this would throw since the parse fails.

@quexten quexten changed the title feat(crypto): tolerate unparseable EncStrings fix(crypto): tolerate unparseable EncStrings Sep 9, 2026
@quexten quexten changed the title fix(crypto): tolerate unparseable EncStrings fix(crypto): tolerate unparseable EncStrings across FFI boundary Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🔍 SDK Breaking Change Detection

SDK Version: unparseable-encstring (7c0e6b0)

⚠️ If breaking changes are detected, a corresponding pull request addressing them must be ready for merge in the affected client repository.

Client Status Details
typescript ❌ Breaking changes detected Compilation failed with new SDK version. A corresponding pull request addressing the breaking changes must be ready for merge in bitwarden/clients. - View Details

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.
Results update as workflows complete.

dani-garcia
dani-garcia previously approved these changes Sep 9, 2026

@dani-garcia dani-garcia left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, thanks!


/// Never returns an error: anything that does not match a known format becomes
/// [EncString::Unparseable]. Use [EncString::parse_strict] to reject those instead.
fn from_str(s: &str) -> Result<Self, Self::Err> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking improvement: I wonder if we should provide some From<&str> and From<String> implementations, to allow conversion without having the parent do error handling. In the future it might be nice if the API bindings returned the EncString type directly for example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Will leave it to follow-up, but:
In the future it might be nice if the API bindings returned the EncString type directly for example. is something I'd very much like to enable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's something I've looked into the past a few times. The server already has the [EncryptedString] annotation, and I've already created a filter to make sure the data makes it into the schema: https://github.com/bitwarden/server/blob/main/src/SharedWeb/Swagger/EncryptedStringSchemaFilter.cs#L14

With that done, it's a single config like to get openapi to emit a custom type in the rust code, unfortunately the fact that server has a single EncString annotation while the SDK splits it based on Symmetric/Asymmetric makes it a bit hard as it is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVM I see i missed a test and failed to run test before sending the PR :(
I'll add your suggestion now since I'll have to get re-approval anyways.

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.17647% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.18%. Comparing base (c1b859a) to head (e6b7ca5).
⚠️ Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
...rates/bitwarden-crypto/src/enc_string/symmetric.rs 90.26% 11 Missing ⚠️
crates/bitwarden-crypto/src/keys/master_key.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1462      +/-   ##
==========================================
+ Coverage   86.01%   86.18%   +0.17%     
==========================================
  Files         525      534       +9     
  Lines       77769    79831    +2062     
==========================================
+ Hits        66891    68803    +1912     
- Misses      10878    11028     +150     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change bug Something isn't working t:fix Fix a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants