Skip to content

macOS Save throws errSecDuplicateItem (-25299) when another writer creates the same item between find and add #183

Description

@matt-edmondson

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.

Activity

  1. matt-edmondson commented on Oct 6, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Recommended next step: Wait for #182 to merge, then add the errSecDuplicateItem retry into find-and-modify on top of it, so the two changes don't conflict.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions