sshpassx 0.2.0

A secure, drop-in Rust replacement for sshpass with macOS Keychain and 1Password integration
# sshpassx

A drop-in Rust replacement for [sshpass](https://sourceforge.net/projects/sshpass/) with secure password storage via macOS Keychain and 1Password.

## Quick Start

```sh
# Install
cargo install sshpassx

# Store a password once, use it everywhere
sshpassx --store user@host        # prompts for password, saves to Keychain
sshpassx -k ssh user@host         # looks up stored password automatically
sshpassx -k scp user@host:f .     # works with scp, rsync, sftp, etc.

# Or pass a password directly (original sshpass style)
sshpassx -p mypass ssh user@host
```

**Using 1Password instead of macOS Keychain?**

```sh
export SSHPASSX_BACKEND=op
sshpassx --store user@host
sshpassx -k ssh user@host
```

---

## Installation

```sh
# From crates.io
cargo install sshpassx

# Or from source
cargo install --path .

# Or build manually
cargo build --release
# Binary at: target/release/sshpassx
```

## Configuration

### Backend Selection

sshpassx supports two password storage backends, controlled by environment variables:

| Variable | Values | Description |
|----------|--------|-------------|
| `SSHPASSX_BACKEND` | `op`, `1password` | Use 1Password instead of macOS Keychain. Unset = macOS Keychain. |
| `SSHPASSX_VAULT` | vault name | 1Password vault to use (optional, defaults to personal vault) |

> **Backward compatibility:** The legacy `SSHPASS_RS_BACKEND` and `SSHPASS_RS_VAULT` environment variables are still accepted as fallbacks.

**macOS Keychain** (default) — passwords are stored in the system Keychain, encrypted at rest, protected by your login password.

**1Password** — passwords are stored as tagged items in your 1Password vault. Requires the [1Password CLI (`op`)](https://1password.com/downloads/command-line/) to be installed and authenticated.

> On non-macOS platforms, macOS Keychain is unavailable. Set `SSHPASSX_BACKEND=op` to use 1Password instead.

#### 1Password Service Account (for automation)

```sh
export OP_SERVICE_ACCOUNT_TOKEN=ops_...
export SSHPASSX_BACKEND=op
sshpassx -k ssh user@host
```

### Verbose / Diagnostic Output

Use `-v` to see which backend is selected, what commands are executed, and how password resolution proceeds:

```sh
sshpassx -v -k ssh user@host
```

```
SSHPASSX: checking SSHPASSX_BACKEND environment variable
SSHPASSX: selected OS keychain backend
SSHPASSX: using keychain with key 'user@host'
SSHPASSX: querying backend for key 'user@host'
SSHPASSX: key 'user@host' found in backend
SSHPASS searching for password prompt using match "assword:"
SSHPASS detected password prompt
SSHPASS sending password
```

## Usage

```
sshpassx [OPTIONS] <command> [args...]
sshpassx --store <key>
sshpassx --delete <key>
sshpassx --list
sshpassx --help
```

### Password Source Flags

Mutually exclusive — only one per invocation.

| Flag | Description |
|------|-------------|
| `-p <password>` | Pass the password directly as an argument |
| `-f <filename>` | Read the password from a file (first line) |
| `-d <number>` | Read the password from a file descriptor |
| `-e` | Read the password from the `SSHPASS` environment variable |
| `-k` | Look up the password from the configured backend, auto-deriving the key from the SSH command |

No flag = read password from stdin (original sshpass behavior).

### Password Management

Standalone operations — no wrapped command needed.

| Flag | Description |
|------|-------------|
| `--store <key>` | Prompt for a password and store it under `<key>` |
| `--delete <key>` | Delete the stored entry for `<key>` |
| `--list` | List all entries managed by sshpassx |
| `--key <value>` | Explicit key name for `-k` (overrides auto-detection) |

### Other Flags

| Flag | Default | Description |
|------|---------|-------------|
| `-P <prompt>` | `assword:` | Prompt pattern to match in PTY output |
| `-v` | off | Verbose mode — diagnostic output to stderr |
| `-h`, `--help` | | Context-sensitive help (try `--store --help`, `--list --help`, `-k --help`) |

### Key Auto-Detection

With `-k` (no `--key`), the key is derived from the wrapped command:

- `ssh user@host` → key = `user@host`
- `ssh -l user host` → key = `user@host`

If neither pattern matches, you're prompted interactively.

## Examples

### Basic Password Passing

```sh
# Direct password
sshpassx -p hunter2 ssh user@host

# From file
sshpassx -f ~/.ssh/mypassword ssh user@host

# From environment variable
SSHPASS=hunter2 sshpassx -e ssh user@host

# From stdin
echo "hunter2" | sshpassx ssh user@host
```

### Keychain Workflow

```sh
# Store once
sshpassx --store user@host

# Use everywhere
sshpassx -k ssh user@host
sshpassx -k scp user@host:/remote/file ./local/
sshpassx -k rsync -avz user@host:/data/ ./backup/

# Explicit key name
sshpassx --key myserver ssh -l user host

# Manage stored entries
sshpassx --list
sshpassx --delete user@host
```

### 1Password Workflow

```sh
export SSHPASSX_BACKEND=op

# Store in 1Password
sshpassx --store user@host

# Use from 1Password
sshpassx -k ssh user@host

# Use a specific vault
SSHPASSX_VAULT=Production sshpassx -k ssh user@host

# List / delete
sshpassx --list
sshpassx --delete user@host
```

### Advanced

```sh
# Custom prompt pattern (non-English systems)
sshpassx -p hunter2 -P "Passwort:" ssh user@host

# Verbose diagnostics
sshpassx -v -k ssh user@host
```

## Exit Codes

| Code | Name | Meaning |
|------|------|---------|
| 0 | Success | Command completed successfully |
| 1 | InvalidArguments | Missing required argument |
| 2 | ConflictingArguments | Multiple password sources or bad flags |
| 3 | RuntimeError | PTY, spawn, keychain, or I/O error |
| 4 | ParseError | Could not parse child process output |
| 5 | IncorrectPassword | SSH rejected the password |
| 6 | HostKeyUnknown | Host key not in known_hosts |
| 7 | HostKeyChanged | Host key changed since last connection |

## Security

| Property | Original sshpass | sshpassx |
|----------|-----------------|------------|
| Password in args (`-p`) | Visible in `ps` | Visible in `ps` (same) |
| Password in file (`-f`) | Plaintext file | Plaintext file (same) |
| Password in env (`-e`) | Cleared before exec | Cleared before exec |
| Keychain storage | Not supported | macOS Keychain (encrypted at rest) |
| 1Password storage | Not supported | `op` CLI (encrypted vault, biometric unlock) |
| In-memory handling | Plain string | `SecretString` (zeroized on drop) |

**Recommendation:** avoid `-p` — the password is visible in `ps`. Use `-k` with stored passwords for scripts and automation.

## Compatibility

### Same as original sshpass

- `-p`, `-f`, `-d`, `-e` password sources
- `-P` prompt pattern matching
- `-v` verbose mode
- Exit codes 0–7
- Works with any PTY-based password prompt (`ssh`, `scp`, `sftp`, `rsync`, etc.)

### New in sshpassx

- `-k` / `--key` — backend-backed password lookup
- `--store` / `--delete` / `--list` — password management
- 1Password backend via `SSHPASSX_BACKEND=op`
- Auto-detection of `user@host` from SSH arguments
- Interactive fallback when key is missing
- Context-sensitive `--help`
- Cross-platform backend guard (non-macOS → requires 1Password)

## Limitations

- macOS Keychain backend is macOS-only. On other platforms, use `SSHPASSX_BACKEND=op`.
- 1Password backend requires the [`op` CLI]https://1password.com/downloads/command-line/.
- No config file — all options via command line and environment variables.