Skip to content

feat: cache role credentials in files, keep only the session in the keyring - #39

Draft
alukach wants to merge 2 commits into
mainfrom
feat/sts-creds-in-file
Draft

alukach wants to merge 2 commits into
mainfrom
feat/sts-creds-in-file

Conversation

@alukach

@alukach alukach commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Stacked on #22 (per-environment caches). Retarget to main once #22 merges.

Problem

macOS keychain entries trust only the binary that created them. Release binaries aren't signed with a Developer ID, so after every CLI upgrade the Keychain asks for the user's password once for each entry: the login session plus one per role, per environment. A user with a couple of roles across prod and staging gets a burst of password prompts after each upgrade.

Change

  • Role credentials (STS keys) go in the 0600 cache files, not the keyring. They're short-lived and re-created from the session, much like the AWS CLI's own ~/.aws/cli/cache. A creds call that finds fresh credentials never touches the keyring.
  • The login session, which holds the long-lived refresh token, stays in the keyring (with the existing file fallback on headless machines).
  • No logout on upgrade: role reads try the file first, then the keyring, so credentials cached by earlier versions are still found. The next login writes a file that takes precedence.
  • Entries from v0.3.0 or earlier that carry their own refresh token keep being written to the keyring, so a refresh token never lands in a plain file.
  • README: the storage section now describes the split.

Result: after an upgrade, one keychain prompt per environment (for the session), and only when credentials actually need re-creating.

Testing

  • cargo test and cargo test --features staging: 30 passed, including the new role_entries_round_trip_through_their_file
  • cargo clippy --all-targets: clean
  • Manual: creds with HOME pointed at a temp dir holding only a file-cached entry prints it, with no keychain access
  • Not exercised against a real keychain, to avoid password prompts: the fallback read of entries written by earlier versions. It's the same keyring read as before, now running only when the file is missing.

🤖 Generated with Claude Code

@claude

claude Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Claude finished @alukach's task in 15s —— View job


✅ No blocking issues — safe to merge.

I read the diff and traced the read, write, delete and login paths. I did not run the tests or build.

  • New login entries have refresh: None (src/main.rs:937, src/main.rs:1034). They go to the file, and forget_keyring_credentials only removes old entries. It can't delete a refresh-bearing keyring entry just written, so the ordering in run_login is safe.
  • delete_slot removes both the keyring entry and the file, so logout leaves no copy behind.
  • write_role removes the stale file after a refresh-bearing entry is written to the keyring. That stops read_role replaying a spent refresh token.

Non-blocking notes:

  • forget_keyring_credentials (src/cache.rs:339) calls delete_credential() on every login, even when no keyring entry exists. On macOS that can still trigger a Keychain access prompt, which this PR is meant to reduce. This only happens on login, so it is rare.
  • When creds renews a role that was read from the keyring, the old keyring entry stays until the next login. If the file is deleted later, the stale entry is read again. The README already says this, so the behavior is documented.

Simplify (ponytail)

  • src/cache.rs, write_slot / read_slot: if only the session uses these, they could be renamed to write_session / read_session. The "generic slot" wording no longer fits, and the T: Serialize/DeserializeOwned generics could be dropped if the session is the only user.
  • src/cache.rs, keyring_location(): a one-use helper. It could be inlined, or write_keyring could return the location.

💰 Estimated review cost: $0.13 · 0m15s · 5 turns

Base automatically changed from feat/per-environment-cache to main October 13, 2026 22:36
alukach and others added 2 commits November 4, 2026 12:56
…eyring

STS credentials are short-lived and re-minted from the login session, so
they don't need the keyring. Keeping them out of it means a creds call
with fresh credentials never touches the keyring, and macOS asks for the
keychain password after each upgrade for one entry (the session) instead
of one per role.

Reads try the file first and fall back to the keyring, so credentials
cached by earlier versions keep working. Entries that still carry their
own refresh token (v0.3.0 and earlier) stay in the keyring.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es at login

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alukach
alukach force-pushed the feat/sts-creds-in-file branch from 972bcf2 to a10eb7d Compare November 4, 2026 19:57

This branch has not been deployed

No deployments
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.

1 participant