Metadata-Version: 2.4
Name: keepassxc-ssh-agent
Version: 1.1.0
Summary: SSH IdentityAgent proxy that triggers KeePassXC database unlock via TouchID
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: keepassxc-browser-api==0.1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Dynamic: license-file

# KeePassXC SSH Agent

`keepassxc-ssh-agent` is an SSH `IdentityAgent` proxy for macOS that automatically triggers KeePassXC database unlock (via TouchID / Quick Unlock) when an SSH key is needed.

Similar to how [Strongbox](https://strongboxsafe.com/) handles SSH keys, this tool sits between your SSH client and the system `ssh-agent`. When SSH requests a key that isn't loaded (because the KeePassXC database is locked), the proxy triggers KeePassXC's unlock dialog. After you authenticate with TouchID, KeePassXC pushes the keys to `ssh-agent`, and the SSH operation continues seamlessly.

![Functionality demonstration](assets/demo.gif)

## Prerequisites

- **macOS** (uses Unix sockets and KeePassXC's browser extension socket)
- **Python >= 3.10**
- **KeePassXC** with:
  - Browser Integration enabled (Settings > Browser Integration > Enable browser integration)
    ![KeePassXC Browser Integration Settings screenshot](assets/settings-browser-integration.png)
  - SSH Agent Integration enabled (Settings > SSH Agent > Enable SSH Agent integration)
    ![SSH-Agent settings screenshot](assets/settings-ssh-agent.png)
  - SSH keys configured with "Add key to agent when database is opened/unlocked"
    ![SSH-Key entry settings screenshot](assets/ssh-key-entry-settings.png)

## Usage

```
usage: keepassxc-ssh-agent [-h] [--socket SOCKET] [--config CONFIG]
                           [--timeout TIMEOUT] [-v]
                           {install,run,status,uninstall} ...

SSH IdentityAgent proxy that triggers KeePassXC database unlock via TouchID

positional arguments:
  {install,run,status,uninstall}
    install             Associate with KeePassXC and install LaunchAgent
    run                 Start the SSH agent proxy (default command)
    status              Check connection status with KeePassXC
    uninstall           Remove LaunchAgent and restore SSH_AUTH_SOCK

options:
  -h, --help          show this help message and exit
  --socket SOCKET     Path for the agent Unix socket
                      (default: ~/.keepassxc/agent.sock)
  --config CONFIG     Path to config file
                      (default: ~/.keepassxc/ssh-agent.json)
  --timeout TIMEOUT   Timeout in seconds for unlock prompt (default: 30)
  -v, --verbose       Enable verbose logging
```

## How It Works

```
SSH Client ──► SSH agent protocol ──► keepassxc-ssh-agent (proxy)
                                             │
                                             ├─► SSH agent protocol ──► System ssh-agent
                                             │   (forward requests / replay after unlock)
                                             │
                                             └─► Browser extension protocol ──► KeePassXC
                                                 (trigger unlock when keys missing)
```

1. SSH client connects to the proxy socket and requests identities or a signature
2. Proxy forwards the request to the system `ssh-agent`
3. If `ssh-agent` returns keys/signature, proxy passes it through (no delay)
4. If `ssh-agent` returns empty/failure (DB is locked, keys not loaded):
   - Proxy connects to KeePassXC via the browser extension protocol
   - Sends `get-databasehash` with `triggerUnlock` to show the unlock dialog
   - Polls until the database is unlocked or timeout expires
   - KeePassXC pushes SSH keys to `ssh-agent` on unlock
   - Proxy retries the original request and returns the result

### SSH_AUTH_SOCK Interception

The proxy automatically intercepts `SSH_AUTH_SOCK` on startup by renaming the system ssh-agent socket (e.g. `/tmp/com.apple.launchd.XXX/Listeners`) to a `.system` backup and placing a symlink from the original path to the proxy socket. All SSH clients then connect to the proxy transparently. The proxy forwards requests to the renamed `.system` socket.

On shutdown, the proxy restores the original socket. No separate LaunchAgent or SSH config is needed — the `run` command handles everything.

## Install

Make sure KeePassXC is running and unlocked with browser integration enabled, then:

### Homebrew (recommended)

See **[homebrew homepage](https://brew.sh/)** on how to setup homebrew.

```shell
brew install mietzen/tap/keepassxc-ssh-agent
keepassxc-ssh-agent install --register-only
brew services start keepassxc-ssh-agent
```

This will:
- Install `keepassxc-ssh-agent` and its dependencies
- Register with KeePassXC (you'll need to approve the association in the unlocked KeePassXC window)
- Start the background service via Homebrew (auto-starts on login)

### pipx - Automatic Install

See **[pipx installation guide](https://github.com/pypa/pipx#install-pipx)** on how to setup pipx.

```shell
pipx install keepassxc-ssh-agent
keepassxc-ssh-agent install -y
```

This will:
- Generate encryption keys for the browser protocol
- Request association with KeePassXC (you'll need to approve it in the KeePassXC window)
- Save the agent configuration to `~/.keepassxc/ssh-agent.json`
- Save the browser API credentials to `~/.keepassxc/browser-api.json` (shared with `keepassxc-cli` if installed)
- Create a LaunchAgent for auto-start on login

The `-y` flag auto-accepts all prompts. Without it, you'll be asked interactively whether to create the LaunchAgent.

### pipx - Manual Install

If you want more control over the installation, you can split it into steps:

#### 1. Install the package

```shell
pipx install keepassxc-ssh-agent
```

#### 2. Register with KeePassXC

Register with KeePassXC without creating a LaunchAgent:

```shell
keepassxc-ssh-agent install --register-only
```

You'll need to approve the association in the KeePassXC window when prompted.

#### 3. Create a LaunchAgent (optional)

To auto-start the agent on login, create a LaunchAgent plist:

```shell
cat << 'EOF' > ~/Library/LaunchAgents/org.keepassxc.ssh-agent.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>org.keepassxc.ssh-agent</string>
  <key>ProgramArguments</key>
  <array>
    <string>/path/to/keepassxc-ssh-agent</string>
    <string>run</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/tmp/keepassxc-ssh-agent.out.log</string>
  <key>StandardErrorPath</key>
  <string>/tmp/keepassxc-ssh-agent.err.log</string>
</dict>
</plist>
EOF
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.keepassxc.ssh-agent.plist
```

Replace `/path/to/keepassxc-ssh-agent` with the actual path (find it with `which keepassxc-ssh-agent`).

Or start manually without a LaunchAgent:

```shell
keepassxc-ssh-agent run
```

## Uninstall

### Homebrew

```shell
brew services stop keepassxc-ssh-agent
brew uninstall keepassxc-ssh-agent
rm -rf ~/.keepassxc  # Remove config (optional)
```

### pipx

```shell
keepassxc-ssh-agent uninstall -y
pipx uninstall keepassxc-ssh-agent
```

This stops and removes the LaunchAgent, restores the original SSH_AUTH_SOCK socket, and removes the config directory (`~/.keepassxc/`). Without `-y`, you'll be asked before deleting the config directory.

### Manual Uninstall

#### 1. Stop and remove the LaunchAgent

```shell
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/org.keepassxc.ssh-agent.plist 2>/dev/null
rm -f ~/Library/LaunchAgents/org.keepassxc.ssh-agent.plist
```

#### 2. Restore SSH_AUTH_SOCK

If the agent was running, it restores the original socket on shutdown automatically. If the system socket is still symlinked (e.g. after a crash), reboot or restore manually:

```shell
# Find the original socket path
SYSTEM_AGENT=$(cat ~/.keepassxc/ssh-agent.json | python3 -c 'import json,sys; print(json.load(sys.stdin).get("system_agent_path",""))')
# Remove the symlink and restore the backup
rm -f "$SYSTEM_AGENT"
mv "${SYSTEM_AGENT}.system" "$SYSTEM_AGENT"
```

#### 3. Remove config and socket

```shell
rm -rf ~/.keepassxc
```

#### 4. Uninstall the package

```shell
pipx uninstall keepassxc-ssh-agent
```

## Known Limitations

- **macOS only**: Uses KeePassXC's browser extension Unix socket at `$TMPDIR/org.keepassxc.KeePassXC.BrowserServer`
- **DB unlocked but agent cleared**: If the database is already unlocked but `ssh-agent` keys were manually removed (`ssh-add -D`), the proxy detects empty keys and triggers "unlock", but KeePassXC reports "already unlocked" without reloading keys. Workaround: lock and re-unlock the database in KeePassXC.
- **Multiple databases**: `triggerUnlock` only works for the currently active database tab in KeePassXC.

## Development

This package depends on [`keepassxc-browser-api`](https://github.com/mietzen/keepassxc-browser-api), which handles the KeePassXC browser extension protocol. The browser API credentials are stored in `~/.keepassxc/browser-api.json` and are shared with `keepassxc-cli` if installed.

```shell
# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run tests with coverage
pytest --cov=keepassxc_ssh_agent
```
