Skip to content

fix(auth): auto-recover from credential decryption failures after upgrade#398

Open
zerone0x wants to merge 1 commit intogoogleworkspace:mainfrom
zerone0x:fix/auth-decryption-recovery
Open

fix(auth): auto-recover from credential decryption failures after upgrade#398
zerone0x wants to merge 1 commit intogoogleworkspace:mainfrom
zerone0x:fix/auth-decryption-recovery

Conversation

@zerone0x
Copy link
Contributor

Summary

Fixes #389

After upgrading from a pre-keyring version to v0.11+, users encounter Failed to decrypt credentials: Decryption failed errors that persist even after logout + login cycles. The root cause is that the encryption key can change between versions (the keyring migration in #345 could delete the .encryption_key file while the keyring entry might be lost later), leaving credentials.enc encrypted with a now-unavailable key.

Changes:

  • auth.rs: When credentials.enc fails to decrypt, automatically remove the stale file and associated token caches, then return a clear error directing the user to gws auth login. This breaks the "stuck after logout+login" cycle by ensuring stale credentials don't block recovery.
  • credential_store.rs: When the OS keyring returns a valid encryption key but no .encryption_key backup file exists, create one. This prevents future key loss if the keyring becomes unavailable (container restart, daemon change, OS upgrade).

Root Cause Analysis

  1. Pre-keyring versions stored the encryption key only in ~/.config/gws/.encryption_key
  2. The v0.11 keyring migration (fix(credential_store): stop persisting encryption key file when keyring is available #345) moved the key to the OS keyring and deleted the file
  3. If the keyring later becomes unavailable (flaky D-Bus, different session, etc.), a new key is generated
  4. Existing credentials.enc is encrypted with the old (now lost) key and cannot be decrypted
  5. The old code returned a hard error with no automatic recovery, leaving users stuck

Test plan

  • cargo check passes
  • cargo clippy passes with no warnings
  • cargo test — all 552 tests pass
  • New test: test_load_credentials_corrupt_encrypted_file_is_removed — verifies stale credentials are cleaned up on decryption failure
  • New test: keyring_backend_creates_file_backup_when_missing — verifies file backup is created when keyring has key but file is missing
  • Manual test: simulate upgrade scenario (encrypt with one key, switch key, verify auto-recovery)

🤖 Generated with Claude Code

…rade

When the encryption key changes between versions (e.g. after the v0.11
keyring migration in googleworkspace#345), credentials.enc becomes undecryptable. The
old behavior returned a hard error with no recovery path, leaving users
stuck even after logout+login cycles.

Changes:
- auth.rs: When credentials.enc fails to decrypt, automatically remove
  the stale file (and associated token caches) and return a clear error
  directing the user to re-authenticate. This ensures the next `gws auth
  login` starts fresh with the current encryption key.
- credential_store.rs: When the keyring returns a valid key but no
  .encryption_key file exists, create a file backup. This prevents key
  loss if the keyring becomes unavailable later (e.g. container restart,
  OS upgrade, daemon change).

Fixes googleworkspace#389

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zerone0x zerone0x requested a review from jpoehnelt as a code owner March 11, 2026 08:42
@changeset-bot
Copy link

changeset-bot bot commented Mar 11, 2026

⚠️ No Changeset found

Latest commit: 927e89b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@google-cla
Copy link

google-cla bot commented Mar 11, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue where users encountered persistent credential decryption failures after upgrading, particularly when the encryption key changed or became unavailable due to keyring migration. The changes introduce robust error handling to automatically clear stale credentials and improve the resilience of the encryption key storage, preventing users from getting stuck in an unrecoverable state and ensuring smoother transitions across application versions.

Highlights

  • Automatic Credential Recovery: Implemented automatic recovery for credential decryption failures by removing stale encrypted files and token caches, guiding users to re-authenticate with a clear error message.
  • Encryption Key Backup: Enhanced encryption key resilience by ensuring a file backup of the encryption key is created if the OS keyring provides the key but the local file is missing, preventing future key loss.
Changelog
  • src/auth.rs
    • Modified load_credentials_inner to catch decryption errors, remove credentials.enc and associated token caches, and provide a clear error message instructing the user to log in again.
    • Added test_load_credentials_corrupt_encrypted_file_is_removed to verify the automatic removal of undecryptable credential files.
  • src/credential_store.rs
    • Updated resolve_key to create a backup .encryption_key file if a key is successfully retrieved from the keyring but the file backup is absent.
    • Added keyring_backend_creates_file_backup_when_missing test to confirm the creation of the key file backup.
Activity
  • cargo check passes.
  • cargo clippy passes with no warnings.
  • cargo test — all 552 tests pass.
  • New test test_load_credentials_corrupt_encrypted_file_is_removed was added.
  • New test keyring_backend_creates_file_backup_when_missing was added.
  • Manual test to simulate upgrade scenario is planned.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Generative AI Prohibited Use Policy, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust auto-recovery mechanism for credential decryption failures, which is a significant improvement for user experience after upgrades. The changes automatically clean up stale credential files and provide clear instructions to the user. Additionally, it improves key persistence by creating a file backup of the keyring key. My main feedback is to correct some misleading comments in the error handling logic to ensure code clarity and maintainability.

Comment on lines +213 to +216
// Decryption failed — the encryption key likely changed (e.g. after
// an upgrade that migrated keys between keyring and file storage).
// Remove the stale file so the next `gws auth login` starts fresh,
// and fall through to other credential sources.
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The comment on line 216 is misleading. It states that the code will "fall through to other credential sources", but the anyhow::bail! macro on line 229 causes the function to return an error immediately, preventing any fall-through. This makes the code's control flow difficult to understand. The code's behavior (bailing out) is correct, but the comment should be updated to reflect this.

Suggested change
// Decryption failed — the encryption key likely changed (e.g. after
// an upgrade that migrated keys between keyring and file storage).
// Remove the stale file so the next `gws auth login` starts fresh,
// and fall through to other credential sources.
// Decryption failed — the encryption key likely changed (e.g. after
// an upgrade that migrated keys between keyring and file storage).
// Remove the stale file so the next `gws auth login` starts fresh.

let sa_token_cache = enc_path.with_file_name("sa_token_cache.json");
let _ = std::fs::remove_file(&sa_token_cache);

// Fall through to remaining credential sources below.
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This comment is also misleading. The anyhow::bail! macro on the next line will cause the function to return an error immediately, preventing any fall-through to other credential sources. This comment should be removed to avoid confusion about the control flow.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auth broken after update

2 participants