Skip to content

macOS Keychain Integration

On macOS, ZestSSH uses the system Keychain to store sensitive data securely. This is handled transparently through FlutterSecureStorage, which maps to the macOS Keychain Services API.

DataKeychain Key Pattern
Identity passwordsidentity_password_{id}
Identity private keysidentity_key_{id}
Security PIN hashsecurity_pin_hash
Lock methodsecurity_lock_method
Auto-lock timeoutsecurity_auto_lock_timeout
PIN failed attemptspin_failed_attempts
PIN lockout timerpin_lockout_until
Auto-backup passwordauto_backup_password
Sync state dataVarious sync-related keys

All of this data is encrypted by the macOS Keychain using the user’s login keychain password, which is typically the same as their macOS user password.

FlutterSecureStorage on macOS uses the Security framework’s Keychain Services API:

  • Items are stored as generic passwords (kSecClassGenericPassword).
  • Each item is scoped to the app’s keychain access group, preventing other apps from reading ZestSSH secrets.
  • The keychain is locked when the user logs out and unlocked on login.

ZestSSH’s macOS build includes the following entitlements:

ZestSSH’s macOS build is not sandboxed — there is no com.apple.security.app-sandbox entitlement. It is distributed outside the Mac App Store as a developer/sysadmin tool. Running unsandboxed is the standard approach for macOS SSH clients (Termius, iTerm2, Royal TSX): it gives the app access to the user’s existing ~/.ssh/config and known_hosts files and avoids the errSecMissingEntitlement keychain errors that sandboxing caused on non-App-Store signing.

The macOS build does not declare a com.apple.security.keychain-access-groups entitlement. Because the app is not sandboxed, Keychain Services automatically uses the app’s own bundle identifier as the access group, scoping keychain items to ZestSSH without any explicit $(AppIdentifierPrefix) configuration.

Network Client (com.apple.security.network.client)

Section titled “Network Client (com.apple.security.network.client)”

Allows outbound network connections — required for SSH, SFTP, Mosh, cloud sync, and backup destinations.

Network Server (com.apple.security.network.server)

Section titled “Network Server (com.apple.security.network.server)”

Allows inbound network connections — required for local port forwarding (the app acts as a local server that forwards traffic through the SSH tunnel).

Because the app is not sandboxed, it does not declare the com.apple.security.files.user-selected.read-write entitlement (unnecessary without the sandbox). File operations still work normally, including:

  • Importing SSH keys
  • Importing/exporting backup files
  • Importing SSH config files
  • Exporting terminal transcripts

JIT Compilation (com.apple.security.cs.allow-jit, Debug/Profile only)

Section titled “JIT Compilation (com.apple.security.cs.allow-jit, Debug/Profile only)”

Present only in the debug/profile entitlements, not in release builds. Allows Just-In-Time compilation for Flutter’s debug mode.

Hardened Runtime is enabled in the Xcode project settings. macOS apps distributed outside the Mac App Store must use Hardened Runtime for notarization. ZestSSH enables Hardened Runtime, which provides:

  • Code signing enforcement — Only signed code can be loaded.
  • Library validation — Only Apple-signed or same-team-signed libraries can be loaded.
  • Memory protection — Restricts access to other processes’ memory.

The macOS build is not sandboxed, so Hardened Runtime — not the App Sandbox — provides this process isolation.

The first time ZestSSH writes to the keychain, macOS may prompt the user to allow keychain access. Once approved, subsequent access is automatic.

Keychain items are not automatically removed when the app is deleted. If you reinstall ZestSSH, previously stored secrets may still be available in the keychain. To fully remove all ZestSSH data, manually delete keychain items via Keychain Access.app.

Keychain items are scoped to the macOS user account. If multiple users share a Mac, each user’s ZestSSH secrets are isolated in their own login keychain.

FlutterSecureStorage items are stored in the local keychain, not iCloud Keychain. SSH credentials are not synced across devices via iCloud. Use ZestSSH Juiced for cross-device synchronization.

  • The macOS Keychain encrypts items at rest using AES-256.
  • Items are only accessible when the keychain is unlocked (user is logged in).
  • Keychain items are scoped to ZestSSH’s own access group (its bundle identifier), so other apps cannot read them.
  • FlutterSecureStorage does not cache decrypted values in memory — each read is a fresh keychain query.