What's wrong
MacOsCredentialStore.Save (CredentialCache/Storage/MacOsCredentialStore.cs:81-129) is find-then-modify-or-add: it calls SecKeychainFindGenericPasswordWithRef, and on errSecItemNotFound calls SecKeychainAddGenericPassword, throwing CredentialStoreException on any non-zero add status (126-129).
That's a check-then-act race. If another writer creates the same service/account item after the find but before the add, the add returns errSecDuplicateItem (-25299) and Save throws. The per-persona lock in CredentialCache.cs (~:144, :201) only serializes within one CredentialCache instance, so it doesn't cover two processes, two cache instances, or direct ICredentialStore users.
The Windows (CredWrite) and Linux (secret_password_store_sync) stores are both native upserts and can't fail this way, so on macOS alone ICredentialStore.Save isn't reliably the "store or replace" it's documented as.
Failure scenario
Two processes (or two MacOsCredentialStore instances) save the same persona for the first time concurrently. Both finds return not-found; the second add fails and the caller gets CredentialStoreException: SecKeychainAddGenericPassword failed (-25299). (Found by reading the code; not run on macOS.)
Suggested fix / acceptance criteria
- Add
errSecDuplicateItem = -25299 to NativeMethods.
- When the add returns
errSecDuplicateItem, retry the find-by-ref + SecKeychainItemModifyAttributesAndData branch once, then throw only if that also fails.
- Concurrent
Save calls on separate instances never throw, and the stored value afterwards equals one of the writes.
- Cover the retry branch with a test (e.g. the IL-scan style used by
NativeStoreScrubbingWiringTests, or a seam around the add status).
Note: open PR #182 (for #166) modifies the same Save method, so whoever picks this up should build on top of it.
What's wrong
MacOsCredentialStore.Save(CredentialCache/Storage/MacOsCredentialStore.cs:81-129) is find-then-modify-or-add: it callsSecKeychainFindGenericPasswordWithRef, and onerrSecItemNotFoundcallsSecKeychainAddGenericPassword, throwingCredentialStoreExceptionon any non-zero add status (126-129).That's a check-then-act race. If another writer creates the same service/account item after the find but before the add, the add returns
errSecDuplicateItem(-25299) andSavethrows. The per-persona lock inCredentialCache.cs(~:144,:201) only serializes within oneCredentialCacheinstance, so it doesn't cover two processes, two cache instances, or directICredentialStoreusers.The Windows (
CredWrite) and Linux (secret_password_store_sync) stores are both native upserts and can't fail this way, so on macOS aloneICredentialStore.Saveisn't reliably the "store or replace" it's documented as.Failure scenario
Two processes (or two
MacOsCredentialStoreinstances) save the same persona for the first time concurrently. Both finds return not-found; the second add fails and the caller getsCredentialStoreException: SecKeychainAddGenericPassword failed (-25299).(Found by reading the code; not run on macOS.)Suggested fix / acceptance criteria
errSecDuplicateItem = -25299toNativeMethods.errSecDuplicateItem, retry the find-by-ref +SecKeychainItemModifyAttributesAndDatabranch once, then throw only if that also fails.Savecalls on separate instances never throw, and the stored value afterwards equals one of the writes.NativeStoreScrubbingWiringTests, or a seam around the add status).Note: open PR #182 (for #166) modifies the same
Savemethod, so whoever picks this up should build on top of it.