Skip to content

fix(auth): verify keyring roundtrip before deleting .encryption_key#379

Closed
alexriftagentic wants to merge 1 commit intogoogleworkspace:mainfrom
alexriftagentic:fix/keyring-migration-key-loss
Closed

fix(auth): verify keyring roundtrip before deleting .encryption_key#379
alexriftagentic wants to merge 1 commit intogoogleworkspace:mainfrom
alexriftagentic:fix/keyring-migration-key-loss

Conversation

@alexriftagentic
Copy link

@alexriftagentic alexriftagentic commented Mar 10, 2026

Summary

Fixes the credential decryption bug that makes gws unusable on macOS (and some Linux environments) after gws auth login.

  • Root cause: get_or_create_key() in credential_store.rs deletes the .encryption_key file after entry.set_password() returns Ok(()). On macOS with ad-hoc signed binaries (e.g. npm-installed), the Keychain write silently fails to persist — so the next process invocation can't read the key back, generates a new random key, and existing credentials.enc becomes permanently undecryptable.
  • Symptom: "encryption_error": "Could not decrypt. May have been created on a different machine." immediately after a successful gws auth login, on the same machine.

Changes

  1. Migration path (existing file key → keyring): After set_password(), verify the roundtrip with get_password(). Only delete .encryption_key if readback matches.
  2. New key generation: Always write to the local .encryption_key file first (source of truth), then optionally store in keyring. Previously it wrote to keyring only and skipped the file if set_password() returned Ok.
  3. Keyring-read-success path: Stop proactively deleting the .encryption_key file when keyring read succeeds — keep it as a durable backup for environments where keyring access is intermittent.

Test plan

  • cargo check passes
  • On macOS (Apple Silicon, npm install): gws auth logingws auth status shows encryption_valid: true
  • On macOS: .encryption_key file is preserved after login
  • On Linux with no keyring daemon: same flow works via file fallback
  • Existing unit tests pass (encrypt_decrypt_round_trip, etc.)

Fixes

Closes #360, #364, #367, #375, #274

🤖 Generated with Claude Code

On macOS (and some Linux environments), the OS keyring `set_password()`
returns Ok(()) even when the entry is not actually persisted — e.g. for
ad-hoc signed npm-installed binaries that lack Keychain entitlements.

The previous code deleted the `.encryption_key` file after a seemingly
successful keyring write. On the next process launch, `get_password()`
would fail to find the entry, the file was gone, so a new random key was
generated — permanently losing access to existing credentials.enc.

This commit makes three changes:

1. Migration path: after writing to keyring, verify the roundtrip by
   reading back immediately. Only delete the file if readback matches.

2. New key generation: always write to the local file first, then
   optionally store in keyring. The file is the source of truth.

3. Keyring-read-success path: stop deleting the file copy when the
   keyring read succeeds — keep it as a durable backup.

Fixes googleworkspace#360, googleworkspace#364, googleworkspace#367, googleworkspace#375, googleworkspace#274
@changeset-bot
Copy link

changeset-bot bot commented Mar 10, 2026

⚠️ No Changeset found

Latest commit: 330b2aa

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 10, 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 credential decryption bug that rendered gws unusable on macOS and certain Linux environments after gws auth login. The core issue stemmed from get_or_create_key() prematurely deleting the .encryption_key file, even when silent failures occurred during Keychain writes on macOS. The changes ensure robust key persistence by verifying keyring writes and maintaining a local file backup, thereby preventing existing credentials from becoming permanently undecryptable.

Highlights

  • Migration Path Improvement: The migration process for existing file keys to the keyring now includes a verification step. The .encryption_key file is only deleted if a successful roundtrip (write and read back) to the keyring is confirmed.
  • New Key Generation Logic: When generating a new encryption key, it is now always written to the local .encryption_key file first, establishing it as the primary source of truth. Storing the key in the keyring is now an optional, secondary step.
  • Keyring Read Success Behavior: The system no longer proactively deletes the .encryption_key file when a keyring read succeeds. This ensures the file remains as a durable backup, particularly for environments where keyring access might be intermittent or unreliable.
Changelog
  • src/credential_store.rs
    • Removed the proactive deletion of .encryption_key when a keyring read succeeds, ensuring the file is kept as a backup.
    • Implemented a keyring roundtrip verification during key migration, only deleting the .encryption_key if the key can be successfully written and read back from the keyring.
    • Modified the new key generation process to always save the key to the local .encryption_key file first, then optionally attempt to store it in the keyring.
Activity
  • No specific human activity (comments, reviews) has been recorded for this pull request yet.
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

The pull request modifies the get_or_create_key function in src/credential_store.rs to enhance the robustness and reliability of encryption key storage. The changes prioritize file-based storage as a primary backup mechanism and introduce more resilient interactions with the OS keyring. Specifically, when a key is successfully read from the keyring, the local key file is now retained as a backup instead of being removed. During key migration from a file to the keyring, a roundtrip verification step has been added: the file is only removed if the key can be successfully written to and read back from the keyring, preventing potential data loss. Furthermore, when a new key is generated, it is now always persisted to a local file first, with keyring storage becoming an optional, secondary step for convenience, ensuring the key is never lost even if keyring operations fail.

@jpoehnelt
Copy link
Member

Superseded by #359 which takes a different approach: instead of verifying the keyring roundtrip, it always preserves .encryption_key as a durable fallback and adds GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file for environments without a keyring. Thank you for the contribution! See #359

@jpoehnelt jpoehnelt closed this Mar 10, 2026
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.

macOS: Credential decryption always fails - OS Keyring not storing encryption key

4 participants