You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(credential-store): mirror .encryption_key instead of deleting it
The keyring backend in resolve_key() was auto-deleting the .encryption_key
fallback file when a key was found in the OS keyring. The function
docstring explicitly stated the file should never be deleted ('it always
serves as a durable fallback for environments where the keyring is
ephemeral'), but the code contradicted the doc.
This caused a TOCTOU bug: when a user switched backends (e.g. from
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file to default keyring), the keyring
would return a stale entry from before the file backend was used, then
delete the .encryption_key file (which contained the actual current key).
This made credentials.enc undecryptable, triggering a second cleanup
that deleted the credentials themselves — silently wiping the user's
authenticated session.
The fix mirrors the keyring key to the .encryption_key file (keeping the
file in sync with the keyring) instead of deleting it. Per Gemini code
review, the mirror uses the existing save_key_file (atomic_write with
0o600 permissions + fsync) and ensure_key_dir (0o700 on parent) helpers
to keep file permissions restrictive and the write race-free. A second
optimization (also per Gemini) only invokes the fsync when the file
content does not already match the keyring key, eliminating per-invocation
disk wear on the common case. Error messages are sanitized via
sanitize_for_terminal. This matches the function docstring's design
intent and prevents the silent-wipe bug.
Tests (29/29 pass):
- Renamed 'cleans_up_legacy_file_*' to 'mirrors_legacy_file_*' to
reflect the new behavior
- Updated assertions: file now exists after the operation and contains
the keyring key
- New: 'keyring_backend_skips_write_when_file_already_has_correct_key'
verifies the fsync is skipped when the file matches (via mtime check)
- New: 'keyring_backend_writes_when_file_has_stale_key' verifies the
file IS updated when the content doesn't match
- Linux fallback tests already mirrored this behavior; macOS/Windows
tests now match
Reported-by: Alvin Chang (alvin@goodciso.org)
Original-patch-author: Cole (AI closer agent, goodciso.org)
Tested-by: Cole (Cole, 2026-06-12)
Co-authored-by: Cole <cole-closer@goodciso.org>
Co-authored-by: Alvin Chang <alvin.chang@gmail.com>
Fix silent auth wipe: the keyring backend in `resolve_key()` was auto-deleting the `.encryption_key` fallback file when a key was found in the OS keyring, contradicting the function's own docstring. The fix mirrors the keyring key to the file (via `save_key_file` + `ensure_key_dir` for atomic write with 0o600 permissions and 0o700 parent) instead of deleting it. Switching backends mid-session no longer wipes credentials.
0 commit comments