Repository navigation
fix(oauth): store keyring tokens as one entry per provider #668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
euxaristia
wants to merge
46
commits into
Twigpine:main
from
euxaristia:fix/keyring-oauth-per-provider-entries
Closed
Changes from 41 commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
3e024a5
fix(oauth): store keyring tokens as one entry per provider
euxaristia 1e15a4b
fix(oauth): lock keyring reads against concurrent Save/Delete
euxaristia ec252e3
fix(oauth): make the keyring token store bounded, recoverable, and mi…
euxaristia bb48e7a
test(oauth): assert Status also stays lock-free behind a crashed writ…
euxaristia 7017b2f
fix(oauth): recover legacy tokens across an interrupted keyring migra…
euxaristia 98ff17b
fix(oauth): lease the keyring lock with wall-clock time
euxaristia 95faae0
fix(oauth): bound the keyring index chunk count before reading
euxaristia 1cc9a11
fix(oauth): scope the keyring fallback lock to a per-user path
euxaristia 46f858e
fix(oauth): fail logout on legacy-blob delete failure; cap index chun…
euxaristia 9e12e6d
fix(oauth): use wall clock for lock timeout and bound keyring index
euxaristia 64707c5
fix(oauth): refuse keyring indexes over the reader key cap on write
euxaristia a418471
fix(oauth): derive the keyring lock path from keyring identity, not f…
euxaristia 3a0b470
fix(oauth): refuse to delete the legacy keyring blob on a transient r…
euxaristia e3d7868
fix(oauth): dedupe and validate the keyring index before fanning out …
euxaristia 163dd91
fix(oauth): anchor the keyring lock on home dir and honor the legacy …
euxaristia d778466
fix(oauth): preserve token scopes across refresh and encode keyring l…
euxaristia c817cf6
fix(oauth): address review findings on legacy freshness, unindexed ke…
euxaristia a99ff78
fix(oauth): fix readKeyIndex chunked path rawKeys rename and cap order
euxaristia 20d077e
test(oauth): cover read() index/entry desync recovery for chunked ind…
euxaristia 5e91dc1
chore: force CodeRabbit re-review
euxaristia b011d7b
fix(oauth): address PR requested changes for keyring per-provider ent…
euxaristia 45bba7b
fix(oauth): address review findings for per-provider keyring entries
euxaristia 26ffa43
fix(oauth): address remaining keyring migration P1s
euxaristia 0cf4072
fix(oauth): harden keyring index write and migration safety
euxaristia fe87d04
fix(oauth): freeze legacy keyring and tombstone durable deletes
euxaristia f095ac9
Harden OAuth migration state transitions against interrupted writes.
euxaristia 8c365a8
fix(oauth): close lease panics, ownership refresh, and review findings.
euxaristia b7d850a
fix(oauth): preserve incomplete index chunks and tighten lease owners…
euxaristia c5cf244
fix(oauth): fence lost leases, protect missing index chunks, and boun…
euxaristia d7b3cd3
fix(oauth): anchor the Windows fallback keyring lock to the caller's …
euxaristia 496ee39
fix(oauth): fence keyring writes at every step, not just around the c…
euxaristia 913a970
fix(oauth): honor a logout performed by a running pre-migration binary
euxaristia 95fe65c
fix(oauth): preflight index chunk capacity before writing any chunk
euxaristia 6ae0da9
fix(oauth): stage index chunk growth under a new generation before pu…
euxaristia 3627902
Address review feedback for keyring OAuth per-provider entries.
euxaristia 4505f8d
fix(oauth): close remaining CodeRabbit findings on keyring store
euxaristia 735d5b4
fix(oauth): address review feedback on keyring per-provider entries
euxaristia 5b9457e
Address reviewer feedback on durable logout, legacy origin lifecycle,…
euxaristia a667b3a
Freeze the legacy keyring entry on every write path.
euxaristia 3bc5493
fix(oauth): propagate lock cleanup errors and discard staged chunks o…
euxaristia e9fe983
fix(oauth): verify lock release token and reconcile legacy origin on …
euxaristia fccb2a4
fix(oauth): extract shared marker set helpers and overwrite stale in-…
euxaristia 8ef5d53
fix(oauth): preserve indexed tokens on transient legacy read failure
euxaristia ff24df1
Keep legacyOrigin on token refresh so old-binary logout stays visible.
euxaristia d6acef2
Finish the mutationIntent contract across every token persist path.
euxaristia 82e4c17
Document the keyring persist contract and lock MCP refresh to refresh…
euxaristia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| //go:build !windows | ||
|
|
||
| package oauth | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "syscall" | ||
| ) | ||
|
|
||
| // isLockCreateContention reports whether a failed O_EXCL lock create should be | ||
| // treated as contention (another holder's lock exists) rather than a hard | ||
| // error. On non-Windows platforms the only contention errno is EEXIST | ||
| // (os.ErrExist); EACCES (os.ErrPermission) is a genuine permission failure and | ||
| // must surface immediately rather than spin to the lock timeout. | ||
| func isLockCreateContention(err error) bool { | ||
| return errors.Is(err, os.ErrExist) | ||
| } | ||
|
|
||
| // checkOAuthLockDirOwner rejects a fallback lock directory not owned by the | ||
| // current user: on a shared temp root another user could have pre-created the | ||
| // path and would then control its lifetime (deletion/renaming), permanently | ||
| // denying OAuth keyring operations. | ||
| func checkOAuthLockDirOwner(info os.FileInfo) error { | ||
| stat, ok := info.Sys().(*syscall.Stat_t) | ||
| if !ok { | ||
| return errors.New("oauth lock fallback directory ownership metadata unavailable") | ||
| } | ||
| if int(stat.Uid) != os.Geteuid() { | ||
| return fmt.Errorf("oauth lock fallback directory is owned by uid %d, not the current user", stat.Uid) | ||
| } | ||
| return nil | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // identityLockRoot is never called on non-Windows: keyringFallbackLockDir | ||
| // resolves the uid-anchored home and the fixed /tmp root directly. It exists so | ||
| // the shared fallback can name one helper without a build-tagged call site. | ||
| func identityLockRoot() (string, error) { | ||
| return "", fmt.Errorf("oauth: identityLockRoot is windows-only") | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| //go:build windows | ||
|
|
||
| package oauth | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| "golang.org/x/sys/windows" | ||
| ) | ||
|
|
||
| // isLockCreateContention reports whether a failed O_EXCL lock create should be | ||
| // treated as contention (another holder's lock exists) rather than a hard | ||
| // error. On Windows, a concurrent holder's os.Remove leaves the lock file in a | ||
| // "delete pending" state, so the O_EXCL create races it with | ||
| // ERROR_ACCESS_DENIED (os.ErrPermission) rather than os.ErrExist; both are | ||
| // contention here. | ||
| func isLockCreateContention(err error) bool { | ||
| return errors.Is(err, os.ErrExist) || errors.Is(err, os.ErrPermission) | ||
| } | ||
|
|
||
| // checkOAuthLockDirOwner is a no-op on Windows: identityLockRoot resolves a | ||
| // per-user location from the process token rather than a shared root, so there | ||
| // is no co-tenant directory to validate ownership of. | ||
| func checkOAuthLockDirOwner(os.FileInfo) error { | ||
| return nil | ||
| } | ||
|
|
||
| // identityLockRoot resolves the last-resort keyring lock directory from the | ||
| // caller's own identity instead of the environment. os.TempDir() is not usable | ||
| // here for the same reason the Unix branch refuses it: it resolves %TMP%, then | ||
| // %TEMP%, then %USERPROFILE%, and the first two are launcher-controlled, so two | ||
| // processes of one user can compute different lock paths while writing the same | ||
| // fixed keyring account and race it. Being per-user by default is not the | ||
| // property that matters; being stable for that user is. | ||
| // | ||
| // SHGetKnownFolderPath reads the user's profile location through the process | ||
| // token, so it ignores %LOCALAPPDATA% and every other temp override. Failing | ||
| // closed is deliberate: without a stable identity there is no lock path two | ||
| // processes are guaranteed to agree on, and a guessed one silently reintroduces | ||
| // the race it is meant to prevent. | ||
| func identityLockRoot() (string, error) { | ||
| base, err := windows.KnownFolderPath(windows.FOLDERID_LocalAppData, 0) | ||
| if err != nil { | ||
| return "", fmt.Errorf("resolve LocalAppData for keyring lock: %w", err) | ||
| } | ||
| dir := filepath.Join(base, "zero", "oauth-locks") | ||
| if err := os.MkdirAll(dir, 0o700); err != nil { | ||
| return "", err | ||
| } | ||
| return dir, nil | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.