Skip to content

Prevent ProtectedData crash on Linux by catching PlatformNotSupportedException#384

Merged
imnasnainaec merged 8 commits into
masterfrom
fix/password-platform-support
Jun 25, 2026
Merged

Prevent ProtectedData crash on Linux by catching PlatformNotSupportedException#384
imnasnainaec merged 8 commits into
masterfrom
fix/password-platform-support

Conversation

@imnasnainaec

@imnasnainaec imnasnainaec commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

`ServerSettingsModel.EncryptPassword` and `DecryptPassword` use `System.Security.Cryptography.ProtectedData`, which compiles for .NET 6 but throws `PlatformNotSupportedException` at runtime on Linux.

Guard both methods with `!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)` and return `null` early on non-Windows platforms, following the same pattern ProtectedData itself uses. This avoids using exceptions for predictable control flow.

On Windows, both methods also catch runtime failures:

  • `EncryptPassword` returns `null` on `CryptographicException` (broken DPAPI subsystem) — same effect as `RememberPassword = false`.
  • `DecryptPassword` returns `null` on `CryptographicException` (different Windows user account, OS reinstall) and `FormatException` (invalid Base64 in the stored value) rather than the raw input. Returning the input unmodified would pass a DPAPI-encrypted base64 blob as a credential on a Windows-to-Linux settings migration, causing silent auth failures.

Known limitation: on non-Windows platforms, `RememberPassword = true` silently becomes a no-op — the preference is lost on restart with no UI feedback. Addressing that properly (disabling the checkbox or surfacing a message on unsupported platforms) requires UI changes and is left for a follow-up.

Trade-off: when `EncryptPassword` catches `CryptographicException` on Windows and returns `null`, `SaveUserSettings` will store `null` and call `Settings.Default.Save()`, permanently wiping a previously persisted password. Before this PR, the exception would propagate and `Save()` would never execute, preserving the old encrypted value. `CryptographicException` from `ProtectedData.Protect` is rare on healthy Windows systems, and the password remains available in-memory via `PasswordForSession` for the current session, but the user will not realize their saved password was dropped until the next restart.

Mitigates #298

Devin review: https://app.devin.ai/review/sillsdev/chorus/pull/384


This change is Reviewable

ProtectedData.Protect/Unprotect compiles for .NET 6 but throws at runtime on
Linux. Wrap both calls in a try/catch: EncryptPassword returns null on failure
(same effect as RememberPassword=false), and DecryptPassword returns its input
unmodified so a partially-stored value does not crash the caller.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imnasnainaec imnasnainaec self-assigned this Jun 22, 2026
imnasnainaec and others added 3 commits June 22, 2026 15:01
Returning the input unmodified would pass a raw DPAPI blob as a password
credential when settings are migrated from Windows to Linux, causing silent
auth failures. Returning null matches the "no saved password" semantics,
consistent with how EncryptPassword already behaves on unsupported platforms.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProtectedData.Unprotect throws CryptographicException when the data was
encrypted by a different Windows user account, after an OS reinstall, or
if the stored value is corrupted. The correct response is the same as for
PlatformNotSupportedException: return null and force the user to re-enter
their password.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecryptPassword

Any failure to encrypt or decrypt a saved password now resolves to null
(treat as no saved password) rather than crashing the caller:
- EncryptPassword: CryptographicException from a broken DPAPI subsystem would
  otherwise abort the entire SaveUserSettings call, not just the password field.
- DecryptPassword: FormatException from Convert.FromBase64String covers the
  case where the stored value is not valid Base64 (manual file corruption).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imnasnainaec imnasnainaec marked this pull request as ready for review June 22, 2026 19:34
@imnasnainaec imnasnainaec requested a review from rmunn June 22, 2026 19:34
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Test Results

       8 files  ±0     333 suites  ±0   2h 22m 22s ⏱️ - 15m 19s
   992 tests +1     936 ✔️ +2    56 💤 ±0  0  - 1 
3 157 runs  +3  3 034 ✔️ +4  123 💤 ±0  0  - 1 

Results for commit 4cdcc38. ± Comparison against base commit 363c8d1.

♻️ This comment has been updated with latest results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imnasnainaec imnasnainaec marked this pull request as draft June 23, 2026 10:33
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imnasnainaec imnasnainaec marked this pull request as ready for review June 23, 2026 17:43
@hahn-kev

Copy link
Copy Markdown
Contributor

If we know that it's going to throw then we should use !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) to return early. This is what ProtectedData uses to check.

…SupportedException

Replace the PlatformNotSupportedException catch with an explicit
!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) early return,
following the same pattern ProtectedData itself uses internally.
Avoids exceptions as control flow for a predictable platform condition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@imnasnainaec imnasnainaec left a comment

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.

Good point! Done.

@imnasnainaec made 1 comment.
Reviewable status: 0 of 3 files reviewed, all discussions resolved (waiting on rmunn).

net462 (.NET Framework) doesn't have RuntimeInformation; guard the
platform check with #if !NETFRAMEWORK. The check is also redundant
there since net462 only runs on Windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@imnasnainaec

Copy link
Copy Markdown
Contributor Author

A possible follow-up issue/pr would be to completely disable the remember-password checkbox on Linux so that it's not a silent no-op.

@imnasnainaec imnasnainaec merged commit 3571e47 into master Jun 25, 2026
7 of 8 checks passed
@imnasnainaec imnasnainaec deleted the fix/password-platform-support branch June 25, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants