What's wrong
In CredentialCache/Storage/MacOsCredentialStore.cs, both Save (~line 81) and Remove (~line 139) call SecKeychainFindGenericPasswordWithRef only to get itemRef. Save then passes it to SecKeychainItemModifyAttributesAndData, and Remove passes it to SecKeychainItemDelete. Both calls still request the password data, though:
passwordLength: out _, passwordData: out IntPtr existingPtr, // Save
passwordLength: out _, passwordData: out IntPtr passwordPtr, // Remove
itemRef: out IntPtr itemRef);
Apple's SecKeychainFindGenericPassword documentation says to pass NULL for passwordLength and passwordData when you only want the item reference. Passing non-null pointers makes the Keychain decrypt the old secret and copy it into the process. The code then releases that buffer with SecKeychainItemFreeContent, which does not zero it.
Why it matters
- It breaks the scrubbing guarantee. Every overwrite or delete puts the previous plaintext credential into process memory and leaves it there unscrubbed. The read path, by contrast, goes to some length to scrub. A secret that is being rotated or deleted is exactly the one you'd least want lingering in memory.
- It can prompt the user. Reading item password data is the operation the Keychain gates behind its "allow access" prompt when the calling binary isn't on the item's ACL, for example after an app update changes its signature. So a plain
Remove, or an overwrite, can raise an authorization dialog it doesn't need. Deleting or modifying through the item ref does not need the secret.
Suggested fix
Add a P/Invoke overload, or change the existing signature, so the password parameters are IntPtr passwordLength, IntPtr passwordData. Pass IntPtr.Zero for both in Save and Remove, and remove the matching SecKeychainItemFreeContent(IntPtr.Zero, existingPtr/passwordPtr) calls.
Acceptance criteria
Save (the update branch) and Remove no longer request password data from the Keychain.
TryGet stays the only macOS path that decrypts a secret, and it keeps its existing scrub-and-free handling.
How this was verified
By comparing the code with Apple's documented parameter semantics. It was not run on macOS. That the old secret is decrypted without being needed is certain from the code. That this can trigger a prompt is documented Keychain behaviour, not something observed here.
What's wrong
In
CredentialCache/Storage/MacOsCredentialStore.cs, bothSave(~line 81) andRemove(~line 139) callSecKeychainFindGenericPasswordWithRefonly to getitemRef.Savethen passes it toSecKeychainItemModifyAttributesAndData, andRemovepasses it toSecKeychainItemDelete. Both calls still request the password data, though:Apple's
SecKeychainFindGenericPassworddocumentation says to passNULLforpasswordLengthandpasswordDatawhen you only want the item reference. Passing non-null pointers makes the Keychain decrypt the old secret and copy it into the process. The code then releases that buffer withSecKeychainItemFreeContent, which does not zero it.Why it matters
Remove, or an overwrite, can raise an authorization dialog it doesn't need. Deleting or modifying through the item ref does not need the secret.Suggested fix
Add a P/Invoke overload, or change the existing signature, so the password parameters are
IntPtr passwordLength, IntPtr passwordData. PassIntPtr.Zerofor both inSaveandRemove, and remove the matchingSecKeychainItemFreeContent(IntPtr.Zero, existingPtr/passwordPtr)calls.Acceptance criteria
Save(the update branch) andRemoveno longer request password data from the Keychain.TryGetstays the only macOS path that decrypts a secret, and it keeps its existing scrub-and-free handling.How this was verified
By comparing the code with Apple's documented parameter semantics. It was not run on macOS. That the old secret is decrypted without being needed is certain from the code. That this can trigger a prompt is documented Keychain behaviour, not something observed here.