GitHub

Keychain 3.0.4

Keychain 3.0.4 is a focused maintenance release that improves validation of persisted SSH agent state and adds a clearer spelling for the hostname override.

  • Hardened pidfile reuse after reboot. A recorded PID must now belong to a running ssh-agent, and its recorded socket must respond as an SSH agent before Keychain will reuse it. This prevents an orphaned socket or a recycled PID from making stale state appear valid; Keychain instead rejects the stale reference and selects or starts a valid agent (fixes #246 and #247).
  • Added --hostname as an alias for --host, making explicit hostname overrides easier to discover while preserving the existing behavior and configuration model.

Keychain 3.0.3

Keychain 3.0.3 is a focused maintenance release that corrects the behavior of the legacy --clear option and --ignore-missing, backed by expanded real-agent regression coverage.

  • Restored --clear as an SSH-only wipe followed by normal coordinated key loading. It now reloads the complete requested SSH key set, including keys that were loaded before the wipe, without flushing GnuPG's passphrase cache (fixes #243).
  • Corrected --ignore-missing when every requested key is absent. Keychain now exits successfully without output, starting an agent, or creating a pidfile; mixed requests continue to load every key that does resolve (fixes #242).
  • Expanded behavioral coverage for SSH identity clearing, reloading, forgetting and listing; ssh-agent stop targets; systemd environment propagation; OpenSSH host configuration; and manual pager controls.

Keychain 3.0.2

Keychain 3.0.2 is a focused maintenance release that improves command-line compatibility and validation, quiet startup behavior, and the visibility of Keychain's core value proposition.

  • Corrected legacy command-line translation so options and their values remain in their original order. This prevents value-taking options such as --timeout 180 from being regrouped and misinterpreted when used with traditional Keychain 2.x-style invocations (PR #236).
  • Implemented missing validation for fixed lists of values. For example, keychain env --shell now rejects unsupported output formats and lists the valid choices instead of silently falling back to another format (fixes #234).
  • Made --quiet pass OpenSSH's native -q option to ssh-add, suppressing successful Identity added reports while preserving passphrase prompts and errors (fixes #237).
  • Copied the visual "Keychain Difference" into the built-in manual, and optimized ordering of sections in the README (Discussion #218).

Keychain 3.0.1

Keychain 3.0.1 continues to improve macOS --confirm UI dialog support. When --confirm is used on macOS, it now implies --no-inherit, to ensure that Keychain is able to initialize its own ssh-agent that is properly configured to use the macOS native Keychain confirm dialog (addresses #227).

In addition, cancelling GPG signing key warming no longer results in control characters being displayed on the terminal (fixes #228).

Added --immediate to skip "Press Enter to initialize keys" prompt (addresses #230). When requested keys are missing, the first Keychain to acquire the lock will run ssh-add immediately instead of first requiring Enter. This is technically safe, but no longer Keychain's default behavior since it's sub-optimal for some user scenarios. This can be enabled persistently via the [agent] immediate = true ~/.keychainrc configuration option.

Corrected keychain man pager integration (addresses #231). Color will be enabled when less is specifically detected, and -R will be enabled when not the default. Otherwise, keychain man output will not have color sequences. This fixes man page output on several systems.

GnuPG Scope Adjustment

Keychain 3.0.1 also includes an important change -- the scope of its GnuPG integration has been deliberately narrowed, for using gpg-agent as a drop-in replacement for ssh-agent has been removed (addresses #164).

While this may seem counterintuitive, this decision was made to improve security. When loading an encrypted SSH key with this feature enabled, ssh-add, invoked by Keychain, prompted for the key's original passphrase. If the key was not already present in GnuPG's private-key store, GnuPG then requested a new passphrase through Pinentry and stored a persistent copy. A user unfamiliar with this behavior who simply wanted to use gpg-agent in place of ssh-agent may not have understood why GnuPG was requesting another passphrase, and not realize that this new passphrase would be used to re-encrypt their private key in GnuPG's on-disk persistent key store, thus duplicating it.

Even more unfortunate, the GnuPG passphrase request for the re-encryption happens right after the user supplied a passphrase for decryption, not as a separate flow, adding to the potential confusion. It's very possible that the user might hit Enter and submit an empty passphrase for the second unexpected prompt, potentially leaving the imported GnuPG copy without passphrase protection on disk.

The conclusion I came to is that GnuPG's ssh-agent protocol compatibility functions are more of an SSH private key importer/bridge which exclusively uses GnuPG's own key store, rather than a drop-in replacement for ssh-agent -- so we shouldn't treat it as if it is a drop-in replacement. While I could instead have tried to smooth over the rough edges with GnuPG, I would be fighting against GnuPG's intended architecture too much, so it's best to simply define a clear boundary of what it makes sense to support and not support.

Keychain will continue to support gpgs:, gpge:, and gpga: for proving and warming native GPG signing and decryption capabilities. You can still use keychain wipe --gpg to flush its in-memory secret cache. This remains supported and does not remove persistent key material. Keychain will invoke GnuPG for those operations, but it will no longer start, configure, adopt, or otherwise manage the gpg-agent lifecycle. Consider Keychain an orchestrator of ssh-agent's lifecycle, and a helpful utility for GnuPG key warming, but no longer responsible for gpg-agent's lifecycle.

This gives Keychain a clear boundary:

  • Keychain manages ssh-agent and SSH keys.
  • Keychain supports warming native GPG signing and decryption capabilities.
  • GnuPG remains responsible for gpg-agent, Pinentry, configuration, and lifecycle.
  • gpg-agent is not supported as a substitute for ssh-agent.

Upgrading will not remove any SSH private keys that were previously imported into GnuPG's persistent key store. Users who previously enabled this behavior should review their GnuPG key storage separately. You can do this by looking in ~/.gnupg/sshcontrol for imported keygrips (40 character hex), and then looking for equivalent ~/.gnupg/private-keys-v1.d/<keygrip>.key files. If gpg-connect-agent 'KEYINFO --ssh-list --ssh-fpr=sha256' /bye lists any keys with "C" in the protection field, it means the key is not protected with a passphrase. It is recommended that you remove these keys via gpg-connect-agent "DELETE_KEY <keygrip>" /bye and then remove the corresponding entry in ~/.gnupg/sshcontrol, making sure you are not deleting any private keys that might be associated with a native OpenPGP key.

The following GnuPG-related changes were made:

  • Removed the behavior previously selected by --ssh-allow-gpg and --ssh-spawn-gpg. The command-line spellings remain accepted as deprecated, warning no-ops so existing scripts continue to run. Inherited GnuPG SSH sockets are ignored, and conventional SSH keys are handled only by ssh-agent.
  • Left gpg-agent startup, configuration, pinentry, and lifecycle entirely to GnuPG. Keychain no longer launches or validates the daemon and no longer exposes gpg_args or KEYCHAIN_GPG_AGENT_ARGS.
  • Changed bare keychain wipe to clear SSH-agent identities only. Flushing gpg-agent's entire in-memory secret cache now requires an explicit --gpg; legacy --wipe all retains its original both-target behavior. No persistent key material is removed.
  • Separated GPG credential warm-up from SSH multi-terminal coordination. Keychain now performs each requested signing or decryption proof exactly once instead of using real signing operations as repeated status probes.
  • Restored --quick as a deterministic SSH-only compatibility shortcut. GPG key arguments are ignored under --quick; Keychain does not invoke GnuPG, while still establishing the SSH-agent environment.
  • Made wipe --gpg authoritative and idempotent. An absent agent is a successful no-op, while missing tooling, timeouts, transport failures, agent errors, and unconfirmed responses now fail with actionable diagnostics.
  • Made GPG credential warm-up authoritative. Signing and decryption failures now identify the affected key and operation, decryption stops immediately when its encryption proof cannot be prepared, and successful decryption is verified against the original plaintext.

Keychain 3.0.0

First stable release of Keychain 3.

Keychain 3 is a ground-up Python 3 evolution of Daniel Robbins' long-running SSH and GPG agent orchestrator. It preserves Keychain's single-file deployment model as a single-file, self-contained keychain.pyz (see Python Rationale), while replacing the historical Bourne shell implementation with a tested, auditable Python package. It requires Python 3.9 or newer and has no third-party runtime dependencies.

The 3.0.0 release incorporates the work delivered through all three public betas. Highlights include:

  • One coordinated agent experience across terminals and sessions. Keychain discovers, validates, starts, and reconnects to a long-running agent per user and host. Managed ssh-agent sockets now live at stable, host-specific paths under ~/.keychain/, avoiding fragile temporary socket directories.

  • Coordinated multi-terminal initialization. When several shells discover missing keys at the same time, they cooperate instead of racing for a lock or displaying duplicate passphrase prompts. Any waiting terminal can take over an inaccessible prompt, and all participants are notified when key loading completes.

  • A modern interface with strong 2.x compatibility. The action-oriented command surface includes add, agent, list, env, inspect, help, and man. Traditional Keychain 2.x invocations remain supported through an explicit compatibility layer; intentional differences are documented under keychain man topic:compat.

  • Broader SSH and GPG workflows. Keychain can load PKCS#11 providers for smartcards and hardware tokens, use or start gpg-agent with SSH support, and explicitly warm GPG signing, encryption, and decryption credentials. Verification failures are reported instead of being mistaken for success.

  • Native macOS confirmation support. When --confirm is used with a new Keychain-managed ssh-agent, Keychain installs a private, confirmation-only osascript helper and gives OpenSSH a zero-dependency, native macOS Allow/Deny dialog for each key use. It works entirely with facilities built into macOS, with no additional askpass package or graphical toolkit to install. Denial, cancellation, a missing desktop session, or helper failure all fail closed. --confirm and --no-gui are intentionally incompatible.

  • Configuration, inspection, and embedded documentation. Persistent preferences may live in an optional ~/.keychainrc; keychain inspect exposes resolved runtime state in human-readable or JSON form; and the complete, versioned manual ships inside the zipapp with topic and option-level help (keychain man and keychain man --list).

  • Hardened state handling and testing. Agent sockets, pidfiles, locks, coordination state, and waiter endpoints are ownership- and permission- checked. The test suite covers modern and legacy CLI behavior, real SSH and GPG integration, multi-terminal coordination, and supported platform differences.

Changes since 3.0.0_beta3:

  • Added zero-dependency, native macOS --confirm dialog support (#222).
  • Made --confirm --no-gui fail explicitly.
  • Fixed an issue where --quiet suppressed the prompt to press Enter to initialize keys (#223).
  • Expanded inspect with .keychainrc status, effective settings and their sources, runtime identity, and relevant environment state. inspect --json now emits a versioned diagnostic report suitable for bug reports.
  • Completed a dedicated security hardening pass across runtime storage, configuration, agent handling, and generated shell output. Keychain now rejects unsafe ownership or permissions on .keychainrc and runtime files, validates SSH endpoints before use, rechecks agent identity before stopping it, writes private state atomically, quotes exports for each target shell, and rejects unsafe control characters.
  • Reworked concurrent initialization around operating-system advisory locks. Lock ownership and liveness no longer depend on PID heuristics; locks are released automatically when the owning process exits, and abandoned activation handoffs are safely reconciled.
  • Hardened the release pipeline with commit-pinned GitHub Actions, verified release artifacts, and automatically generated SHA256 checksums.
  • Converted command timeouts, malformed agent arguments, and operating-system failures into concise user errors instead of Python tracebacks (#224).
  • Corrected askpass environment handling to follow OpenSSH's DISPLAY, WAYLAND_DISPLAY, and SSH_ASKPASS_REQUIRE=force rules.
  • Strengthened GPG warm-up verification so gpga: proves both signing and decryption capability before reporting success, and made decrypt verification portable by avoiding /dev/null as the temporary encrypted payload.
  • Expanded end-to-end SSH confirmation and agent startup coverage.
  • Shortened managed ssh-agent socket names to avoid UNIX-domain socket path limits on macOS, Linux, and other POSIX systems.
  • Extensive code cleanups throughout the codebase (removing deprecated code, simplifying logic where possible, etc.)

macOS --confirm note: Keychain’s native confirmation dialog requires an ssh-agent started by Keychain, because an existing launchd-managed agent cannot receive the required SSH_ASKPASS environment after it has started. Before first enabling confirmation, run keychain agent stop, then invoke Keychain with --no-inherit --confirm. For persistent use, set inherit = false and confirm = true under [agent] in ~/.keychainrc.

Keychain 3.0.0 beta3

Third public beta of Keychain 3.x, collecting changes made after the
3.0.0_beta2 tag.

This release focuses on feature additions and robustness. It makes Keychain
more dependable during shell startup, easier to configure,
supports smartcards or other PKCS#11-backed SSH tokens, closes
a known .keychainrc documentation gap, and significantly enhances
the integrated documentation and documentation rendering.

Highlights:

  • More reliable agent startup. Keychain now keeps its managed ssh-agent
    socket in a stable location under ~/.keychain/ instead of depending on
    temporary /tmp/ssh-* paths. This helps avoid cases where the agent is
    still running but its socket directory has been cleaned up, a problem that
    showed up clearly under WSL but is not unique to it.

  • Better smartcard and hardware-token support. You can now ask Keychain to
    load a PKCS#11 provider directly with pkcs11:/path/to/provider.so. This is
    useful for SSH keys stored on smartcards, security keys, and similar devices.
    This addresses issue #216.

  • Improved Documentation Formatting. Significant improvements in the
    embedded documentation renderer used by keychain man. Pager support
    integrated. Supported .keychainrc config settings are now fully
    documented, streamlined and available. Addresses issue #217.

  • Improved 2.9.8 compatibility details. A few legacy command-line edge
    cases with --stop and --wipe now print a more accurate error message.

  • Copyright has been updated to reflect assignment/ownership by Daniel
    Robbins, the person, removing reference to BreezyOps / Funtoo Solutions,
    Inc.

Keychain 3.0.0 beta2

Second public beta of Keychain 3.x, collecting all changes made after the
3.0.0_beta1 tag.

This release transforms the multi-terminal experience and strengthens GPG key
handling. The headline feature is a coordinated unlock protocol that eliminates
the frustrating "could not acquire lock" errors when multiple shells start
simultaneously -- a common occurrence when Visual Studio Code reconnects to
WSL and restores several terminals at once.

Highlights:

  • Coordinated multi-terminal initialization (solves issue #214). Keychain
    now uses an elegant coordination protocol instead of the classic lock-timeout
    race. When multiple terminals detect missing SSH keys:

    • All terminals display: Press Enter to initialize keys
    • Pressing Enter in any terminal runs ssh-add in that terminal
    • Other terminals wait automatically and are notified when initialization completes
    • Waiting terminals print Keys initialized by another terminal. and configure
      their environment without prompting

    This eliminates the could not acquire lock errors that plagued earlier
    versions. The technical implementation uses a short-lived state lock for
    metadata updates, a dedicated activation lock to elect the loader, and FIFO
    endpoints for instant kernel-level notification (no polling). A takeover
    mechanism allows any waiting terminal to cancel a stuck ssh-add by typing
    takeover, ensuring you're never blocked by a hidden or inaccessible prompt.
    Internal coordination is quiet -- no more Waiting N seconds for lock...
    messages during interactive key loading.

  • Improved startup and key-loading output.

    • Multi-key ssh-add prompts render as compact lists instead of long inline
      messages
    • Common stale pidfile/socket cases (especially in WSL restart scenarios) are
      folded into the Starting ssh-agent... context instead of producing
      separate noisy notes
    • Empty gpg-agent wipe diagnostics no longer render awkward (output: )
      text; non-actionable no-agent details are debug output
    • Successful remote initialization is reported as Keys initialized by another terminal.
  • Reliable GPG warm-up with explicit verification. The gpge:KEYID and
    gpga:KEYID extended key syntax now perform a complete encrypt-then-decrypt
    verification cycle instead of relying on signing warm-up side effects. A tiny
    temporary payload is encrypted to the requested key and immediately decrypted
    through gpg-agent. If this verification cannot be completed, add fails
    rather than reporting success. This is significantly more reliable across
    different GnuPG versions and key configurations, where signing warm-up may
    not populate the decryption passphrase cache. The legacy gpgk:KEYID alias
    remains equivalent to gpgs:KEYID (signing warm-up only).

  • Enhanced documentation. The embedded man page now includes comprehensive
    coverage of the coordination model (keychain man topic:coordination),
    updated guidance for --lockwait and --no-lock options, and clearer
    explanations of GPG warm-up guarantees. New design documents and a formal
    UX acceptance checklist support manual multi-terminal testing.

  • Focused test coverage. New tests validate the coordination state file,
    waiter FIFO registration, activation lock handoff, takeover/cancel mechanics,
    and GPG end-to-end warm-up for both signing and encryption/decryption paths.
    Test infrastructure improvements ensure the checkout's source code is tested
    rather than any installed version, and CI coverage now includes macOS GPG
    validation.

Beta notes:

  • The coordinated unlock flow applies to SSH key loading only. GPG keys use
    explicit warm-up paths (gpgs:, gpge:, gpga:) and do not participate
    in multi-terminal coordination.
  • Terminal prompt erasing is best-effort: used on ANSI-capable terminals,
    falling back to ordinary line output when stderr is redirected, TERM=dumb,
    or the prompt would wrap.

Keychain 3.0.0 beta1

Pre-release

Initial public beta of Keychain 3.x.

Keychain 3 is a ground-up Python 3 rewrite of Daniel Robbins' long-running
SSH/GPG agent manager. The release preserves the traditional single-file
deployment model through keychain.pyz, while replacing the historical
Bourne shell implementation with a tested, auditable Python package.

Highlights:

  • Ships as a standalone keychain.pyz with no third-party runtime
    dependencies.
  • Requires Python 3.9 or newer at runtime; the zipapp bootstrap can re-exec
    into a newer python3.NN on systems where /usr/bin/env python3 is below
    the floor.
  • Adds an action-oriented command surface such as keychain add,
    keychain agent start, keychain agent stop, keychain list,
    keychain env, keychain inspect, keychain help, and keychain man.
  • Keeps keychain 2.x-style invocations working through an explicit
    compatibility layer.
  • Embeds documentation in the zipapp; use keychain man and
    keychain man --list to browse it.
  • Uses a default-deny model for KEYCHAIN_* environment variables; pass
    --allow-env / -E when legacy environment-variable behavior is desired.
  • Releases under GPLv3 for the 3.x series. Keychain 2.x remains GPLv2.

Known beta notes:

  • WSL login-shell startup can run keychain in a noninteractive/no-TTY context
    when invoked by automation. This may fall through to ssh_askpass; stale
    WSL /tmp/ssh-* sockets and hostname-specific pidfiles are tracked for
    follow-up polish.

keychain 2.9.8

keychain 2.9.8 (2 Nov 2025)

This release fixes the release tarball to include all necessary files for building and using keychain.

Bug fixes:

  • Fixed release tarball generation to include bash completion script (completions/keychain.bash),
    Makefile, source files, and other essential components. Previous release (2.9.7) tarball was
    missing these files.
  • Improved tarball generation to use git archive as source of truth, eliminating manual file
    inventory and preventing future omissions.
  • Updated release logic to use dist/ directory for archive generation. GitHub workflow plumbing
    work for new /dist tarball location, associated Makefile and CI fixes.

Documentation:

  • Added bash completion information to keychain man page (NOTES section).

Build Provenance

Artifact SHA256
keychain f8b4e8a2a630907bb81737d455a2dec2cb8308e3210840665239ef9c49bbeadb
keychain.1 3e5150c23ad27ce45e1f77d5f72be3098e6383bb18937e2babffbe39af13f2a2

Tag commit SHA1: 2b3c181eaa73ca27b0cfa3fd12148d6b69e35311

keychain 2.9.7

keychain 2.9.7 (31 Oct 2025)

This release fixes critical issues with spaces in HOME directories and usernames, and adds official Git Bash on Windows compatibility.

Bug fixes:

  • Fixed keychain failures when HOME directory path contains spaces (e.g., C:\Users\John Doe).
    (#188)
  • Fixed username detection for usernames containing spaces (e.g., "Mathew Binkley" on Windows).
    Implemented portable get_owner() function using POSIX-defined ls -ld output format with
    intelligent field parsing to distinguish space-in-username from normal owner/group fields.
  • Fixed pidfile generation to properly quote SSH_AUTH_SOCK paths containing spaces while
    leaving SSH_AGENT_PID unquoted (numeric value). Rewrote write_pidfile() to use robust
    eval-in-subshell approach for extracting variable values from ssh-agent output.
  • All pidfile formats (sh/csh/fish) now correctly handle paths with spaces.
  • Fixed ssh-agent invocation to always use -s option for Bourne-compatible output, simplifying
    pidfile generation and improving compatibility across different environments.
    (#185)

Testing and quality improvements:

  • Added scripts/test-space-home.sh - automated test harness that simulates HOME directories
    with spaces and validates proper handling. Returns proper exit codes for CI integration.
  • Integrated space-in-home test into GitHub Actions release workflow to prevent regressions.
  • Added ShellCheck disable comments with justification for intentional POSIX ls usage.
  • Fixed Unicode arrow characters in comments that caused ShellCheck errors.

New features:

  • Added bash completion support (completions/keychain.bash) with intelligent context-aware
    completion for command-line options, SSH keys, GPG keys, and full --extended mode support.
    Based on work by @mikkoi with significant enhancements for keychain 2.9.x features:
    • Dynamically parses keychain --help for up-to-date option completion
    • Completes SSH key names from ~/.ssh/*.pub files
    • Completes GPG key IDs (8-character short format)
    • --extended mode: sshk:<tab>, gpgk:<tab>, host:<tab> with prefix completion
    • Detects hostnames from ~/.ssh/config for host: completion
    • ShellCheck compliant
      (#186)
  • Added Makefile targets: install-completions and uninstall-completions for optional
    bash completion installation (separate from default install target).
  • Updated RPM spec file (keychain.spec.in) for modern distributions:
    • Modernized description to focus on OpenSSH and GnuPG (removed obsolete ssh.com/Sun SSH)
    • Updated dependencies: sh-utilscoreutils, added Recommends: bash-completion
    • Added bash completion installation to RPM package

Documentation:

  • Updated keychain.pod with detailed implementation notes for space handling, POSIX compliance,
    and the robust eval approach used in pidfile generation.
  • Standardized option ordering in keychain.pod to follow Unix convention (short option first,
    then long option), ensuring compatibility with bash completion regex patterns.
  • Added comprehensive COMPATIBILITY section to keychain.pod documenting:
    • Minimum OpenSSH version (7.3+) and supported features
    • GnuPG 2.1+ requirements for gpg-agent integration
    • Shell compatibility (Bourne/POSIX, csh/tcsh, fish)
    • Git Bash (MSYS2) for Windows - officially documented as supported platform
    • Legacy SSH implementation status (SunSSH, ssh.com)
    • Systemd user environment integration
    • Spaces in HOME and paths handling details
  • Updated README.md with bash completion installation instructions for both system-wide
    and user-only installations.

Build Provenance

Artifact SHA256
keychain ccf51b708de94905403966c467fb7df965016c12b8e30aca458cf5e14129b9ce
keychain.1 ffccaa49f5b8136df211256f0607c440d80957302a079a2c6c6a928cf3404a1d

Tag commit SHA1: dd1ebe4f546bb2088541ae28725f9d2ea2c87325

Read the original on github.com ↗