You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On a cache miss, CredentialCache.TryGet calls Store.TryLoad. On two platforms, the native buffer holding the decrypted credential JSON is freed without being zeroed. Only the managed copy is scrubbed.
Windows, CredentialCache/Storage/WindowsCredentialStore.cs:50-65: CredRead returns a CREDENTIAL whose CredentialBlob (CredentialBlobSize bytes) is the plaintext. The code copies it into blob, and DeserializeAndScrub(blob) scrubs the managed array. The finally then calls NativeMethods.CredFree(handle) on the native block untouched.
Linux is fine: secret_password_free wipes its own copy.
#166 covers the unnecessary decrypt in macOS Save/Remove, and its acceptance criteria deliberately leave TryGet's handling unchanged. This issue is about that TryGet path itself, on both Windows and macOS.
Why it matters
Each load leaves a full plaintext copy of the serialized credential (password or token JSON) in freed process-heap memory until the allocator reuses it. A crash dump or memory scan can recover it. That contradicts the README's scrubbing guarantee and the acceptance criterion of the closed #144: "No path … frees … a buffer holding plaintext … without zeroing it first".
Suggested fix
Add a helper such as NativeSecretBuffer.ZeroExternal(IntPtr ptr, int length) that zeroes a caller-visible native buffer, for example with Marshal.Copy from a zero array, in the same way NativeSecretBuffer.Zero handles its own buffers.
Windows: call it on cred.CredentialBlob / CredentialBlobSize before CredFree.
macOS: call it on passwordPtr / length before SecKeychainItemFreeContent.
Whether CredFree scrubs internally is undocumented. Zeroing a buffer the caller can already read costs almost nothing, so doing it anyway is the safe choice.
Acceptance criteria
Neither the Windows nor the macOS TryLoad frees a native plaintext buffer without zeroing it first, including when deserialization throws. The zeroing belongs in the finally, ahead of the free.
What's wrong
On a cache miss,
CredentialCache.TryGetcallsStore.TryLoad. On two platforms, the native buffer holding the decrypted credential JSON is freed without being zeroed. Only the managed copy is scrubbed.CredentialCache/Storage/WindowsCredentialStore.cs:50-65:CredReadreturns aCREDENTIALwhoseCredentialBlob(CredentialBlobSizebytes) is the plaintext. The code copies it intoblob, andDeserializeAndScrub(blob)scrubs the managed array. Thefinallythen callsNativeMethods.CredFree(handle)on the native block untouched.CredentialCache/Storage/MacOsCredentialStore.cs:51-61:passwordPtr(lengthbytes) is copied and scrubbed the same way, then released withSecKeychainItemFreeContent(IntPtr.Zero, passwordPtr), which does not zero the buffer (as macOS Save and Remove decrypt the existing secret into process memory without using it, and free it unscrubbed #166 notes).secret_password_freewipes its own copy.#166 covers the unnecessary decrypt in macOS
Save/Remove, and its acceptance criteria deliberately leaveTryGet's handling unchanged. This issue is about thatTryGetpath itself, on both Windows and macOS.Why it matters
Each load leaves a full plaintext copy of the serialized credential (password or token JSON) in freed process-heap memory until the allocator reuses it. A crash dump or memory scan can recover it. That contradicts the README's scrubbing guarantee and the acceptance criterion of the closed #144: "No path … frees … a buffer holding plaintext … without zeroing it first".
Suggested fix
NativeSecretBuffer.ZeroExternal(IntPtr ptr, int length)that zeroes a caller-visible native buffer, for example withMarshal.Copyfrom a zero array, in the same wayNativeSecretBuffer.Zerohandles its own buffers.cred.CredentialBlob/CredentialBlobSizebeforeCredFree.passwordPtr/lengthbeforeSecKeychainItemFreeContent.Whether
CredFreescrubs internally is undocumented. Zeroing a buffer the caller can already read costs almost nothing, so doing it anyway is the safe choice.Acceptance criteria
TryLoadfrees a native plaintext buffer without zeroing it first, including when deserialization throws. The zeroing belongs in thefinally, ahead of the free.