[PM-41059] fix: CXF import crash when credential timestamps contain negative values (e.g., Windows FILETIME epoch -11644473600 from Google Password Manager exports). - #7216
Conversation
|
Thank you for your contribution! We've added this to our internal tracking system for review. Details on our contribution process can be found here: https://contributing.bitwarden.com/contributing/pull-requests/community-pr-process. |
| val allCiphers = accountsJsonList.flatMap { accountJson -> | ||
| vaultSdkSource | ||
| .importCxf(userId = userId, payload = accountJson) | ||
| .importCxf(userId = userId, payload = sanitizeTimestamps(accountJson)) |
There was a problem hiding this comment.
Instead of modifying the values received here, it would be better to push this into the SDK so all platforms can get the benefits of this update.
We could apply it in this same way or possibly make the data type signed.
There was a problem hiding this comment.
Let me know if this works:
There was a problem hiding this comment.
Works for me!
The version of the SDK that contains this fix is already merged into the app. It should make it out to the public in the 2026.8.1 release.
Some credential managers (e.g., Google Password Manager) export creationAt/modifiedAt as the Windows FILETIME epoch (-11644473600) when no real date exists. The credential-exchange-format crate deserializes these fields as u64 and rejects negative values. Sanitize the JSON payload before deserialization by clamping negative timestamp values to `null`. ## 🎟️ Tracking bitwarden/android#7140 bitwarden/android#7215 bitwarden/android#7216 https://bitwarden.atlassian.net/browse/PM-40542 ## 📔 Objective ## Summary - Sanitize negative `creationAt`/`modifiedAt` values in CXF payloads before deserialization, clamping them to `null` - Fixes import failures from Google Password Manager on devices where credentials have no real creation/modification date (exported as the Windows FILETIME epoch: -11644473600) ## Root cause The `credential-exchange-format` crate defines these fields as `Option<u64>`, which cannot represent negative values. The fix pre-processes the JSON in `parse_cxf()` before the typed deserialization step. ## Test plan - Added unit tests for `sanitize_timestamps()` covering negative, valid, and mixed timestamp values - Added integration test confirming `parse_cxf()` succeeds with negative timestamps in the payload --------- Co-authored-by: John Harrington <84741727+harr1424@users.noreply.github.com>
|
Corroborating data point on a newer build, with a different negative timestamp
One question on scope: the regex covers Also worth noting for triage: #7175 is the matching report and was closed as Filed as #7311 so this has an open tracking issue. |
🎟️ Tracking
#7140
#7215
https://bitwarden.atlassian.net/browse/PM-40542
📔 Objective
Fixes bug:
Stacktrace:
com.bitwarden.sdk.BitwardenException$Export: v1=com.bitwarden.exporters.ExportException$Cxf: Credential Exchange error: JSON error: invalid value: integer
-11644473600, expected u64 at line 1 column 61701com.bitwarden.sdk.FfiConverterTypeBitwardenError.read(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:174)
com.bitwarden.sdk.FfiConverterTypeBitwardenError.read(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:515)
com.bitwarden.sdk.FfiConverter$DefaultImpls.liftFromRustBuffer(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:11)
com.bitwarden.sdk.FfiConverterRustBuffer$DefaultImpls.liftFromRustBuffer(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:4)
com.bitwarden.sdk.FfiConverterTypeBitwardenError.liftFromRustBuffer(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:1)
com.bitwarden.sdk.FfiConverterTypeBitwardenError.liftFromRustBuffer(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:8)
com.bitwarden.sdk.FfiConverterRustBuffer$DefaultImpls.lift(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:4)
com.bitwarden.sdk.FfiConverterTypeBitwardenError.lift(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:1)
com.bitwarden.sdk.FfiConverterTypeBitwardenError.lift(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:8)
com.bitwarden.sdk.BitwardenException$ErrorHandler.lift(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:6)
com.bitwarden.sdk.BitwardenException$ErrorHandler.lift(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:13)
com.bitwarden.sdk.Bitwarden_uniffiKt.uniffiCheckCallStatus(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:79)
com.bitwarden.sdk.Bitwarden_uniffiKt.access$uniffiCheckCallStatus(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:1)
com.bitwarden.sdk.ExporterClient.importCxf(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:66)
rpb.Z(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:75)
db2.a(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:133)
hnb.T(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:91)
br4.invokeSuspend(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:51)
vo0.resumeWith(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:8)
qw2.run(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:114)
qb3.t0(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:24)
ng1.o(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:93)
ng1.s(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:3)
l41.a(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:7)
j41.R(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:69)
j41.d(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:53)
j41.b(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:104)
com.bitwarden.ui.platform.base.BaseViewModel.trySendAction(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:3)
ip.invokeSuspend(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:1362)
vo0.resumeWith(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:8)
qw2.run(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:114)
xr.p0(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:24)
wr.run(r8-map-id-3711f003cf7d2a46fb8ec7e8ada71c46655423e03b475b325f07a00886353258:3)
android.os.Handler.handleCallback(Handler.java:1095)
android.os.Handler.dispatchMessageImpl(Handler.java:135)
android.os.Handler.dispatchMessage(Handler.java:125)
android.os.Looper.loopOnce(Looper.java:296)
android.os.Looper.loop(Looper.java:397)
android.app.ActivityThread.main(ActivityThread.java:9523)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:575)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:939)
Version: 2026.6.1 (21713)
Device: 📱 google Pixel 9 🤖 17@37 📦 prod
CI: 🧱 commit: e5ee43f
💻 build source: bitwarden/android/actions/runs/28253991408/attempts/1