git-ecosystem/git-credential-manager · error · InteropException

ErrorSecAuthFailed

ErrorSecAuthFailed

Error message

Authorization/Authentication failed.

What it means

InteropException thrown by SecurityFramework.ThrowIfError when the macOS Security framework returns errSecAuthFailed (-25293). It means the keychain rejected an authorization/authentication attempt, e.g. the user entered a wrong password, denied access, or the caller lacks rights to unlock or read the keychain item. The library surfaces raw Security.framework result codes as exceptions when wrapping P/Invoke calls.

Solutions

  1. Unlock the keychain first (open Keychain Access and unlock it, or call SecKeychainUnlock with the correct password).
  2. Reset the item's ACL: delete the existing keychain item and re-create it from this app so the access control list trusts the current binary.
  3. Run inside a logged-in GUI session instead of SSH/launchd headless context, or provision the keychain for CI (security unlock-keychain with correct password).
  4. Verify the app's code signature matches the one that originally stored the item; re-store the password after re-signing.
  5. Check the password/credentials being passed are correct; repeated failures on unlock also surface as errSecAuthFailed.

Example fix

// before: assuming the login keychain is always unlocked
int err = SecKeychainFindGenericPassword(null, serviceLength, service, accountLength, account, out length, out data, IntPtr.Zero);
SecurityFramework.ThrowIfError(err); // throws InteropException -25293 when keychain is locked

// after: unlock the keychain explicitly before querying
byte[] password = Encoding.UTF8.GetBytes(userPassword);
err = SecKeychainUnlock(keychainRef, (uint)password.Length, password, false);
SecurityFramework.ThrowIfError(err);
err = SecKeychainFindGenericPassword(null, serviceLength, service, accountLength, account, out length, out data, IntPtr.Zero);
SecurityFramework.ThrowIfError(err);
Defensive patterns

Strategy: try-catch

Validate before calling

// Check keychain lock state before calling (macOS CLI or via SecKeychainGetStatus)
// security show-keychain-info login.keychain  # fails or prompts if locked
var status = SecKeychainGetStatus(keychainRef, out SecKeychainStatus flags);
bool keychainUnlocked = (flags & SecKeychainStatus.Unlocked) != 0;
if (!keychainUnlocked) UnlockKeychainWithStoredPassword();

Type guard

static bool IsSecAuthFailed(InteropException ex) => ex.ErrorCode == -25293; // errSecAuthFailed

Try / catch

try
{
    ReadCredentialFromKeychain(service, account);
}
catch (InteropException ex) when (ex.ErrorCode == -25293)
{
    // Wrong password / access denied / keychain locked.
    // Re-prompt the user, re-store the credential, or unlock the keychain.
    logger.LogWarning("Keychain auth failed for {Service}", service);
    await ReauthenticateAndReStoreAsync();
}

Prevention

When it happens

Trigger: Calling keychain P/Invokes such as SecKeychainFindGenericPassword, SecKeychainItemCopyContent, SecItemCopyMatching, or SecKeychainAddGenericPassword (via ThrowIfError) when the keychain is locked and the supplied/unlock password is wrong, the user clicked Deny in the access prompt, or the ACL does not grant the calling app access to the item.

Common situations: Running in an SSH/headless session where no GUI user can answer the keychain unlock prompt; a keychain locked with a password different from the login password; the app was re-signed or rebuilt so its code signature no longer matches the item ACL; CI machines with a locked login keychain; wrong credentials passed programmatically to SecKeychainUnlock or item creation.

Understand the failure class

Related errors


AI-assisted analysis of git-ecosystem/git-credential-manager@e8ce762cd0 (2026-09-11). Data as JSON: /api/errors/85d0fc8a5bf2fab9. Report an issue: GitHub.

Appendix: source

Thrown at src/Core/Interop/MacOS/Native/SecurityFramework.cs:146

        public const int ErrorSecAuthFailed = -25293;
        public const int ErrorSecDuplicateItem = -25299;
        public const int ErrorSecItemNotFound = -25300;
        public const int ErrorSecInteractionNotAllowed = -25308;
        public const int ErrorSecInteractionRequired = -25315;
        public const int ErrorSecNoSuchAttr = -25303;

        public static void ThrowIfError(int error, string defaultErrorMessage = "Unknown error.")
        {
            switch (error)
            {
                case OK:
                    return;
                case ErrorSecNoSuchKeychain:
                    throw new InteropException("The keychain does not exist.", error);
                case ErrorSecInvalidKeychain:
                    throw new InteropException("The keychain is not valid.", error);
                case ErrorSecAuthFailed:
                    throw new InteropException("Authorization/Authentication failed.", error);
                case ErrorSecDuplicateItem:
                    throw new InteropException("The item already exists.", error);
                case ErrorSecItemNotFound:
                    throw new InteropException("The item cannot be found.", error);
                case ErrorSecInteractionNotAllowed:
                    throw new InteropException("Interaction with the Security Server is not allowed.", error);
                case ErrorSecInteractionRequired:
                    throw new InteropException("User interaction is required.", error);
                case ErrorSecNoSuchAttr:
                    throw new InteropException("The attribute does not exist.", error);
                default:
                    throw new InteropException(defaultErrorMessage, error);
            }
        }
    }

    [Flags]
    public enum SessionAttributeBits

View on GitHub (pinned to e8ce762cd0)