Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Connecto

AirDrop-like SSH key pairing for your terminal.

Connecto eliminates the hassle of manual SSH key setup. Instead of copying IP addresses and managing keys by hand, simply run connecto listen on one machine and connecto pair on another. Done.

Features

  • mDNS Discovery - Automatically discover devices on your local network
  • VPN Support - Save subnets for cross-network discovery
  • Zero-config Pairing - Exchange SSH keys with a single command
  • Verified Pairing - listen --verify gates key installation behind a 6-digit code bound to the received key
  • Bidirectional Sync - connecto sync on both devices exchanges keys both ways
  • Auto SSH Config - ssh hostname just works after pairing
  • Modern Cryptography - Uses Ed25519 by default (RSA-4096 also supported)
  • Cross-platform - Works on Linux, macOS, and Windows

How it works

┌─────────────────┐                    ┌─────────────────┐
│  Target Machine │                    │  Client Machine │
│                 │                    │                 │
│  connecto listen│◄───── mDNS ───────►│  connecto scan  │
│                 │                    │                 │
│                 │◄── TCP/8099 ──────►│  connecto pair 0│
│                 │                    │                 │
│  authorized_keys│                    │  ~/.ssh/config  │
│     updated     │                    │    updated      │
└─────────────────┘                    └─────────────────┘
                                              │
                                              ▼
                                       ssh mydesktop ✓
  1. Target runs connecto listen - advertises via mDNS
  2. Client runs connecto scan - discovers available devices
  3. Client runs connecto pair 0 - exchanges SSH keys
  4. Done - ssh hostname just works

Quick example

On the target machine (where you want to SSH into):

connecto listen

On the client machine (where you want to SSH from):

connecto scan
connecto pair 0
ssh mydesktop  # It just works!

Next steps

Installation

Connecto can be installed on macOS, Linux, and Windows.

macOS

brew install andreisuslov/connecto/connecto

Binary download

Download the latest release from GitHub Releases:

# Apple Silicon (M1/M2/M3)
curl -LO https://github.com/andreisuslov/connecto/releases/latest/download/connecto-macos-aarch64.tar.gz
tar xzf connecto-macos-aarch64.tar.gz
sudo mv connecto /usr/local/bin/

# Intel Mac
curl -LO https://github.com/andreisuslov/connecto/releases/latest/download/connecto-macos-x86_64.tar.gz
tar xzf connecto-macos-x86_64.tar.gz
sudo mv connecto /usr/local/bin/

Each release also publishes a .sha256 checksum file alongside every archive.

Windows

Run in PowerShell as Administrator:

irm https://raw.githubusercontent.com/andreisuslov/connecto/main/install.ps1 | iex

This will:

  • Download the latest release
  • Install to %LOCALAPPDATA%\connecto
  • Add to PATH (system PATH when run as Administrator, user PATH otherwise)
  • Configure firewall rules for mDNS and the Connecto port (Administrator only)

Chocolatey

choco install connecto

Manual installation

  1. Download connecto-windows-x86_64.zip from GitHub Releases
  2. Extract to a directory of your choice (e.g. %LOCALAPPDATA%\connecto)
  3. Add that directory to PATH

Linux

Binary download

# x86_64
curl -LO https://github.com/andreisuslov/connecto/releases/latest/download/connecto-x86_64-unknown-linux-gnu.tar.gz
tar xzf connecto-x86_64-unknown-linux-gnu.tar.gz
sudo mv connecto /usr/local/bin/

From source

Requires Rust 1.70+:

git clone https://github.com/andreisuslov/connecto
cd connecto
cargo install --path connecto_cli

Verify installation

connecto --version

Shell completions

Enable tab completion for your shell:

# Bash
connecto completions bash >> ~/.bashrc

# Zsh
connecto completions zsh >> ~/.zshrc

# Fish
connecto completions fish > ~/.config/fish/completions/connecto.fish

# PowerShell
connecto completions powershell >> $PROFILE

Restart your shell or source the config file.

Firewall configuration

Connecto uses:

  • UDP 5353 for mDNS discovery
  • TCP 8099 for the pairing protocol

Linux (iptables)

sudo iptables -A INPUT -p udp --dport 5353 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8099 -j ACCEPT

Linux (firewalld)

sudo firewall-cmd --add-port=5353/udp --permanent
sudo firewall-cmd --add-port=8099/tcp --permanent
sudo firewall-cmd --reload

macOS

macOS typically allows these connections by default. If needed, add rules in System Preferences > Security & Privacy > Firewall.

Windows

The PowerShell installer automatically configures firewall rules. For manual setup:

New-NetFirewallRule -DisplayName "Connecto mDNS" -Direction Inbound -Protocol UDP -LocalPort 5353 -Action Allow
New-NetFirewallRule -DisplayName "Connecto TCP" -Direction Inbound -Protocol TCP -LocalPort 8099 -Action Allow

Quick start

Get SSH access between two machines in under a minute.

Prerequisites

Step 1: Start the Listener

On the target machine (the one you want to SSH into):

connecto listen

You’ll see:

  CONNECTO LISTENER

→ Device name: mydesktop
→ Port: 8099

Local IP addresses:
  • 192.168.1.55

✓ mDNS service registered - device is now discoverable

Listening for pairing requests on port 8099...

Step 2: Scan for Devices

On the client machine (the one you want to SSH from):

connecto scan

You’ll see:

  CONNECTO SCANNER

→ Scanning for devices...

✓ Found 1 device(s):

[0] mydesktop (192.168.1.55:8099)

To pair with a device, run: connecto pair <number>

Step 3: Pair

Still on the client machine:

connecto pair 0

You’ll see:

  CONNECTO PAIRING

→ Connecting to 192.168.1.55:8099...
→ Using Ed25519 key (modern, secure, fast)

✓ Pairing successful!

→ Verification code: 627765 — confirm it matches on mydesktop

Key saved:
  • Private: /home/user/.ssh/connecto_mydesktop
  • Public:  /home/user/.ssh/connecto_mydesktop.pub

✓ Added to ~/.ssh/config as 'mydesktop'

You can now connect with:

  ssh mydesktop

Step 4: Connect!

ssh mydesktop

That’s it! The listener exits automatically after successful pairing.

What just happened?

  1. Listener advertised itself via mDNS on your local network
  2. Scanner discovered the listener and displayed it
  3. Pair command:
    • Generated a new Ed25519 SSH key pair
    • Sent the public key to the listener
    • Listener added it to ~/.ssh/authorized_keys
    • Client saved the private key and updated ~/.ssh/config

On a shared network? Run connecto listen --verify instead — the listener will then show a 6-digit code and ask for approval before installing any key. Without --verify, any device on the network can pair. See Security.

Alternative: Bidirectional sync

If you want both devices to be able to SSH to each other, use sync instead:

# Run on BOTH devices at the same time
connecto sync

This exchanges keys bidirectionally - after sync completes, both devices can SSH to each other. See sync command for details.

Next steps

VPN Setup

When devices are on different subnets (common with VPNs), mDNS discovery won’t work across subnets. Connecto provides a simple solution: save the remote subnet once, and scans will include it automatically.

The problem

┌─────────────────────────────────────────────────────────────┐
│                         VPN Network                          │
├─────────────────────────┬───────────────────────────────────┤
│   Subnet A: 10.0.1.0/24 │   Subnet B: 10.0.2.0/24          │
│                         │                                   │
│   ┌─────────────┐       │       ┌─────────────┐            │
│   │   Your Mac  │       │       │   Windows   │            │
│   │  10.0.1.50  │  ✗ mDNS ✗     │  10.0.2.100 │            │
│   └─────────────┘       │       └─────────────┘            │
│                         │                                   │
└─────────────────────────┴───────────────────────────────────┘

mDNS broadcasts don’t cross subnet boundaries, so connecto scan won’t find devices on other subnets.

The solution

Step 1: Find the remote subnet

Ask your colleague or check your VPN documentation for the subnet. Common formats:

  • 10.0.2.0/24 (256 addresses)
  • 192.168.100.0/24
  • 172.16.5.0/24

Step 2: Save the Subnet

connecto config add-subnet 10.0.2.0/24

You can add multiple subnets:

connecto config add-subnet 10.0.3.0/24
connecto config add-subnet 192.168.100.0/24

Step 3: Scan and Pair

Now connecto scan will automatically include saved subnets:

connecto scan
  CONNECTO SCANNER

→ Scanning for devices...

✓ Found 1 device(s):

[0] windows-workstation (10.0.2.100:8099)

Pair as usual:

connecto pair 0

Managing subnets

List saved subnets

connecto config list
Configured subnets:
  • 10.0.2.0/24
  • 10.0.3.0/24

Remove a subnet

connecto config remove-subnet 10.0.3.0/24

Config file location

connecto config path

The config file is stored at:

  • macOS: ~/Library/Application Support/com.connecto.connecto/config.json
  • Linux: ~/.config/connecto/config.json
  • Windows: %APPDATA%\connecto\connecto\config\config.json

One-time subnet scan

If you don’t want to save a subnet permanently, use the --subnet flag:

connecto scan --subnet 10.0.2.0/24

You can specify multiple subnets:

connecto scan -s 10.0.2.0/24 -s 10.0.3.0/24

Listener VPN hint

When someone pairs from a different subnet, the listener shows a helpful message:

✓ Successfully paired with mac-laptop!
  → They can now SSH to this machine.

VPN/Cross-subnet connection detected!
  → Tell mac-laptop to save your subnet for future scans:
    connecto config add-subnet 10.0.1.0/24

Direct pairing

If you know the exact IP, skip scanning entirely:

connecto pair 10.0.2.100:8099

Troubleshooting

Scan takes too long

Scanning large subnets can take time. Connecto scans up to 100 IPs concurrently with 500ms timeout per IP.

For faster scans, use a smaller subnet if possible:

  • /24 = 254 IPs (~2-3 seconds)
  • /22 = 1022 IPs (~5-10 seconds)
  • /16 = 65534 IPs (not recommended)

Connection refused

Ensure:

  1. The target is running connecto listen
  2. Firewall allows TCP 8099
  3. VPN is connected and routing works

Test connectivity:

# Check if port is open
nc -zv 10.0.2.100 8099

listen

Start a listener to accept pairing requests.

Usage

connecto listen [OPTIONS]

Description

The listen command starts a pairing listener on the current machine. It:

  1. Advertises the device via mDNS on the local network
  2. Waits for incoming pairing requests on TCP port 8099
  3. Accepts public keys and adds them to ~/.ssh/authorized_keys
  4. Exits after successful pairing (unless --continuous is used)

Options

OptionDescription
-p, --port <PORT>Port to listen on (default: 8099)
-n, --name <NAME>Device name to advertise (default: hostname)
--verifyRequire interactive approval of a verification code before installing a key
-c, --continuousKeep listening after successful pairing
--adhocCreate an ad-hoc WiFi network (bypasses router, for isolated networks)
--bluetoothEnable Bluetooth Low Energy advertising (Linux only; requires a build with the bluetooth feature)

Examples

Basic usage

connecto listen

Output:

  CONNECTO LISTENER

→ Device name: mydesktop
→ Port: 8099

Local IP addresses:
  • 192.168.1.55

✓ mDNS service registered - device is now discoverable

Listening for pairing requests on port 8099...
connecto listen --verify

Each pairing request must be approved before anything is installed:

Pairing approval required
  • Device:      my-laptop
  • Key comment: user@my-laptop
  • Fingerprint: SHA256:mTiqtUQvo0mB/zDzQbxahaBBq+KJ62pV8ECXptWqzLg
  • Code:        627765
  Compare the code with the one shown on the pairing device.
Approve this pairing? [y/N]

The code is derived from the received key material on both sides independently, so matching codes rule out a swapped key — see Security.

Custom name and port

connecto listen --name workstation --port 9000

Continuous mode

Keep listening for multiple pairings:

connecto listen --continuous

Ad-hoc WiFi network

On networks with client isolation (devices can’t see each other), --adhoc creates a direct device-to-device WiFi network:

connecto listen --adhoc

Platform notes:

  • macOS: modern macOS (14.4 and later) cannot create ad-hoc networks from the command line at all — Apple turned the airport utility into a no-op. Connecto detects this and fails fast with instructions for creating the network manually (Option-click the WiFi menu → Create Network).
  • Linux: uses nmcli (with an iw fallback).
  • Windows: uses netsh wlan hostednetwork (requires Administrator); the network password is printed.

Your previous WiFi network and DHCP configuration are restored when the listener exits, including on Ctrl+C.

What happens during pairing

  1. Client connects and the two sides negotiate a protocol version
  2. Client sends its public key
  3. The listener validates the key and derives its fingerprint and 6-digit verification code
  4. With --verify, the listener prompts for approval — nothing is installed if you decline. Without --verify, the key is installed automatically and the fingerprint/code of what was installed are printed.
  5. Listener adds the key to ~/.ssh/authorized_keys and sends back its username
  6. Listener exits (or continues if --continuous)

If the final confirmation cannot be delivered after the key was installed, the listener rolls the installation back. Probe connections (e.g. from connecto scan) do not consume the one-shot session — the listener keeps waiting until a pairing actually completes.

See Protocol for the full message flow.

VPN/Cross-Subnet Detection

When a pairing comes from a different subnet, the listener displays a helpful message:

✓ Successfully paired with mac-laptop!
  → They can now SSH to this machine.

VPN/Cross-subnet connection detected!
  → Tell mac-laptop to save your subnet for future scans:
    connecto config add-subnet 10.0.1.0/24

Security notes

  • Without --verify, any device on the network that completes the handshake gets its key installed. Use --verify on any network with untrusted devices — see Security.
  • Only run listen when you intend to pair
  • The listener only accepts SSH public keys (not arbitrary data)
  • Stop the listener when done to prevent unwanted pairings

Exit status

Like all connecto commands, listen exits non-zero when it fails (e.g. the port cannot be bound), so it is safe to use in scripts.

scan

Discover devices running connecto listen.

Usage

connecto scan [OPTIONS]

Description

The scan command discovers devices on your network that are running connecto listen. It tries discovery methods in order, falling back when one finds nothing:

  1. mDNS discovery - finds devices advertising the _connecto._tcp service
  2. Subnet scanning - scans your local subnets, saved subnets, and any --subnet arguments
  3. Ad-hoc network scan - looks for Connecto ad-hoc WiFi networks (created with connecto listen --adhoc)
  4. Bluetooth LE - with --bluetooth, scans for BLE-advertised devices (requires a build with the bluetooth feature)

Options

OptionDescription
-t, --timeout <SECONDS>Scan timeout in seconds (default: 5)
-s, --subnet <CIDR>Additional subnet to scan (can be repeated)
--bluetoothEnable Bluetooth Low Energy scanning as a fallback

Examples

Basic scan

connecto scan

Output:

  CONNECTO SCANNER

→ Scanning for devices...

✓ Found 2 device(s):

[0] mydesktop (192.168.1.55:8099)
[1] workstation (192.168.1.100:8099)

To pair with a device, run: connecto pair <number>

Scan additional subnet

connecto scan --subnet 10.0.2.0/24

Scan multiple subnets

connecto scan -s 10.0.2.0/24 -s 10.0.3.0/24

Discovery methods

mDNS Discovery

mDNS (multicast DNS) automatically finds devices on the same subnet. No configuration needed.

Limitations:

  • Only works within the same subnet
  • May be blocked by some network configurations

Subnet scanning

For VPN or cross-subnet scenarios, Connecto scans IP ranges directly. Each responding host is probed with a real protocol handshake, so only actual Connecto listeners show up — see Protocol.

Saved subnets are automatically included:

connecto config add-subnet 10.0.2.0/24
connecto scan  # Now includes 10.0.2.0/24

One-time subnets can be specified with --subnet:

connecto scan --subnet 10.0.2.0/24

Ad-hoc networks

If nothing is found, Connecto looks for ad-hoc WiFi networks created by connecto listen --adhoc. When one is found, Connecto may briefly join it to probe for the listening host — your previous WiFi network is always restored before the results are printed, so pair by rejoining the ad-hoc network when you’re ready.

Network isolation hint

If you have a working network address but nothing answered, your router is likely isolating clients from each other (AP/client isolation). The scan output explains how to work around it with a direct WiFi network.

Device cache

Scan results are cached so that connecto pair <number> can resolve device numbers. The cache lives in your per-user cache directory (e.g. ~/Library/Caches/com.connecto.connecto/devices.json on macOS, ~/.cache/connecto/devices.json on Linux) — not in a world-writable location like /tmp.

Device numbers start at 0 and refer to the most recent scan.

Scan performance

Subnet SizeIPsApproximate Time
/242542-3 seconds
/221,0225-10 seconds
/1665,534Not recommended

Connecto scans up to 100 IPs concurrently with a 500ms timeout per IP.

No devices found?

If no devices are found:

  1. Ensure the target is running connecto listen
  2. Check firewall allows TCP 8099 and UDP 5353
  3. For VPN, add the remote subnet: connecto config add-subnet <CIDR>
  4. Try direct pairing: connecto pair <ip>:8099

See Troubleshooting for more help.

pair

Pair with a discovered device or direct IP.

Usage

connecto pair <TARGET>

Arguments

ArgumentDescription
TARGETDevice number from scan, or direct IP:port

Options

OptionDescription
-k, --key <PATH>Use existing SSH key instead of generating new
-c, --comment <TEXT>Custom key comment
--rsaGenerate RSA-4096 instead of Ed25519

Description

The pair command establishes SSH key-based authentication with a remote device:

  1. Generates a new Ed25519 SSH key pair (or uses an existing key — see below)
  2. Sends the public key to the target device
  3. Displays a 6-digit verification code to compare with the listening device
  4. Saves the private key to ~/.ssh/connecto_<device>
  5. Updates ~/.ssh/config for easy ssh hostname access

Examples

Pair by Device Number

After running connecto scan:

connecto pair 0

Output:

  CONNECTO PAIRING

→ Connecting to 192.168.1.55:8099...
→ Using Ed25519 key (modern, secure, fast)

✓ Pairing successful!

→ Verification code: 627765 — confirm it matches on mydesktop

Key saved:
  • Private: /home/user/.ssh/connecto_mydesktop
  • Public:  /home/user/.ssh/connecto_mydesktop.pub

✓ Added to ~/.ssh/config as 'mydesktop'

You can now connect with:

  ssh mydesktop

The verification code is derived from the key you sent; the listener derives the same code from the key it received. If the listener runs with --verify, its operator compares the codes before approving — matching codes rule out a man-in-the-middle that swapped the key (see Security).

Pair by IP Address

Skip scanning and pair directly:

connecto pair 192.168.1.55:8099

Or with just the IP (uses default port 8099):

connecto pair 192.168.1.55

What gets created

SSH key pair

  • Private key: ~/.ssh/connecto_<device>
  • Public key: ~/.ssh/connecto_<device>.pub

<device> is the listener’s device name sanitized into a lowercase alias (e.g. My Desktopmy-desktop). Keys use Ed25519 by default (modern, secure, fast).

SSH config entry

An entry is added to ~/.ssh/config:

# Added by connecto
Host mydesktop
    HostName 192.168.1.55
    User john
    IdentityFile /home/user/.ssh/connecto_mydesktop

This allows simple ssh mydesktop without specifying user, IP, or key. The # Added by connecto marker identifies the entry as connecto-managed: commands like hosts, unpair, and update-ip only ever touch marked blocks, never your hand-written config.

Re-pairing

If you pair with a device that already has a connecto-managed entry:

  1. The SSH config block is replaced in place
  2. A new key exchange occurs; a freshly generated key overwrites the old key files

This is useful when:

  • The remote machine was reinstalled
  • You want to refresh the keys
  • The IP address changed

Using existing keys

Instead of generating a new key for each pairing, you can use an existing SSH key.

One-time usage

connecto pair 0 --key ~/.ssh/id_ed25519

Both the private key and its .pub file must exist.

Set default key

Set a default key for all future pairings:

connecto config set-default-key ~/.ssh/id_ed25519

Now connecto pair and connecto sync will use this key automatically.

Clear default key

Return to generating new keys:

connecto config clear-default-key

Priority order

When pairing, Connecto looks for keys in this order:

  1. --key flag (if specified)
  2. Config default key (if set)
  3. Generate new key (default behavior)

After pairing

Connect immediately:

ssh mydesktop

Or verify the pairing:

connecto test mydesktop

Exit status

pair exits non-zero when pairing fails (connection refused, rejected by the listener, invalid key, …), so it is safe to chain in scripts:

connecto pair 0 && ssh mydesktop

sync

Bidirectional SSH key pairing between two devices.

Usage

connecto sync [OPTIONS]

Description

The sync command enables two devices to simultaneously exchange SSH keys so both can SSH to each other. Unlike the listen + pair workflow which is one-directional (client can SSH to target), sync establishes bidirectional access.

Both devices run connecto sync at the same time, and they:

  1. Advertise via mDNS (_connecto-sync._tcp.local.)
  2. Scan for sync peers on the network
  3. When found, exchange SSH public keys
  4. Both add each other’s key to ~/.ssh/authorized_keys (only after both sides confirm the exchange)
  5. Both can now SSH to each other

Options

OptionDescription
-p, --port <PORT>Port to use for sync (default: 8099)
-n, --name <NAME>Custom device name (default: hostname)
-t, --timeout <SECS>Peer search timeout in seconds (default: 60)
--rsaUse RSA-4096 key instead of Ed25519
-k, --key <PATH>Use existing SSH key instead of generating new one

Examples

Basic usage

Run on both devices simultaneously:

# On Device A
connecto sync

# On Device B (at the same time)
connecto sync

Output on Device A:

  CONNECTO SYNC

→ Device name: device-a
→ Port: 8099
→ Timeout: 60s

Local IP addresses:
  • 192.168.1.100

→ Using Ed25519 key (modern, secure, fast)
→ Key saved: /Users/alice/.ssh/connecto_sync_device-a

Waiting for sync peer...
Run 'connecto sync' on another device on the same network
Press Ctrl+C to cancel

→ Found peer: Device B (192.168.1.101:8099)
→ Connected to Device B
→ Received key from Device B: bob@device-b
→ Our key was accepted by peer

✓ Sync completed with Device B!
  → Bidirectional SSH access established.
  → You can SSH to them, and they can SSH to you.

Sync Summary:
  • Peer: Device B
  • User: bob
  • Address: 192.168.1.101:8099

Next steps:
  → SSH to peer: ssh device-b

✓ Sync successful!

With custom timeout

For slower networks:

connecto sync --timeout 120

Using an existing key

connecto sync --key ~/.ssh/my_existing_key

sync resolves keys the same way pair does: --key flag first, then the key configured via connecto config set-default-key, then a freshly generated key. The ~/.ssh/config entry written for the peer always points at the key file that actually exists on disk.

Using RSA instead of Ed25519

connecto sync --rsa

How it works

  1. Both devices advertise: each device registers a sync service via mDNS, publishing a random per-run priority as a TXT property (this is also how a device recognizes — and skips — its own advertisement)
  2. Both devices search: each device also searches for other sync services
  3. Priority arbitration picks one direction: whichever device connects sends SyncHello with its priority. The responder accepts only if the initiator’s (priority, device name) pair strictly outranks its own; otherwise it declines and keeps listening, knowing its own outgoing attempt outranks the peer’s. Exactly one direction wins, so starting connecto sync on both devices at the same time converges instead of hanging.
  4. Key exchange: the winning initiator’s SyncHello carries its public key; the responder replies with SyncHelloAck containing its own key
  5. Confirm, then install: both sides exchange SyncComplete and only install the peer’s key after the other side has confirmed — an aborted exchange leaves no key behind
  6. SSH config: each side writes a connecto-managed ~/.ssh/config entry for the peer

See Protocol for the exact message flow.

Comparison with listen + pair

Aspectlisten + pairsync
DirectionOne-wayBidirectional
WorkflowRun listen on target, pair on clientRun sync on both
ResultClient can SSH to targetBoth can SSH to each other
Use caseSetting up access to a serverTwo peers that need mutual access

Protocol messages

The sync protocol uses these message types:

  • SyncHello: Contains version, device name, priority, public key, and SSH user
  • SyncHelloAck: Response with the peer’s public key and acceptance status
  • SyncComplete: Final confirmation of success or failure (sent by both sides)

Troubleshooting

Timeout waiting for sync peer

  • Ensure both devices are on the same network
  • Check that mDNS/Bonjour is not blocked by firewall
  • Try increasing the timeout: connecto sync --timeout 120

Connection refused

  • Make sure both devices start sync around the same time
  • Check that port 8099 is not in use by another service
  • Try a different port: connecto sync --port 9000

Keys not being added

  • Check write permissions on ~/.ssh/authorized_keys
  • Ensure ~/.ssh directory exists with proper permissions (700)

Security notes

  • Sync only with trusted devices on your local network — sync has no equivalent of listen --verify, so any reachable peer that wins arbitration completes the exchange
  • The sync protocol requires both parties to actively participate
  • Keys are installed only after both sides confirm; a failed install on one side prevents the other side from installing too
  • Keys are generated fresh for each sync (unless --key or a default key is configured)
  • Only run sync when you intend to exchange keys with another device

Exit status

sync exits non-zero when the sync fails or times out.

hosts

List all paired hosts.

Usage

connecto hosts

Description

The hosts command displays all devices you’ve paired with using Connecto. It reads ~/.ssh/config and lists the entries marked # Added by connecto; hand-written host blocks are not shown.

Example

connecto hosts

Output:

Paired hosts:

  • mydesktop → john@192.168.1.55
  • workstation → admin@10.0.2.100
  • laptop → alice@192.168.1.42

Connect with:
  → ssh <hostname>

Output fields

FieldDescription
Host aliasName to use with the ssh command
UserUsername for SSH connection
AddressIP address or hostname of the remote machine
CommandDescription
connecto test <host>Test SSH connection
connecto update-ip <host> <ip>Update host’s IP address
connecto unpair <host>Remove pairing
connecto exportBackup all pairings

unpair

Remove a paired host and delete its connecto-generated keys.

Usage

connecto unpair <HOST>

Arguments

ArgumentDescription
HOSTName of the paired host to remove

Description

The unpair command removes a pairing established by Connecto:

  1. Removes the connecto-managed host entry from ~/.ssh/config
  2. Deletes the private and public key files only if the key follows the connecto naming convention (connecto_* / connecto_sync_*)

unpair only operates on entries marked # Added by connecto. Hand-written host blocks in ~/.ssh/config are never touched, even if they share the same alias.

Example

connecto unpair mydesktop

Output:

✓ Removed 'mydesktop' from SSH config.
✓ Deleted private key: /home/user/.ssh/connecto_mydesktop
✓ Deleted public key: /home/user/.ssh/connecto_mydesktop.pub

When the entry uses a personal key

If the pairing was made with --key or a configured default key, the key is not deleted — only the config entry is removed, and connecto says so:

✓ Removed 'mydesktop' from SSH config.
→ Key left untouched (not generated by connecto): /home/user/.ssh/id_ed25519

Notes

  • This only removes the local configuration
  • The public key remains in the remote machine’s ~/.ssh/authorized_keys
  • To fully revoke access, also remove the key from the remote machine (connecto keys remove on that machine)
  • Keys that don’t follow the connecto_* naming convention are never deleted, so a shared personal key is safe

Exit status

unpair exits non-zero if the host is not found among the connecto-managed entries (or no SSH config exists), so scripts can detect the failure.

Re-pairing

After unpairing, you can pair again:

connecto scan
connecto pair 0

A new key pair will be generated and exchanged.

CommandDescription
connecto hostsList all paired hosts
connecto exportBackup pairings before removing

test

Test SSH connection to a paired host.

Usage

connecto test <HOST>

Arguments

ArgumentDescription
HOSTName of the paired host to test

Description

The test command verifies that SSH connectivity works to a paired host. It:

  1. Runs ssh against the host alias (so it uses your ~/.ssh/config entry)
  2. Uses BatchMode (no password prompts) and a 5-second connection timeout
  3. Executes a trivial echo command and checks the response
  4. Reports success or failure

Example

Successful test

connecto test mydesktop

Output:

→ Testing connection to mydesktop...
✓ Connection successful!

Failed test

connecto test mydesktop

Output:

→ Testing connection to mydesktop...
✗ Connection failed.

Troubleshooting:
  • Check if the host is online
  • Verify the IP is correct: connecto hosts
  • Update IP if changed: connecto update-ip mydesktop <new-ip>

A failed test exits non-zero, so it can gate scripts:

connecto test mydesktop && rsync -a project/ mydesktop:project/

Common issues

ErrorCauseSolution
Connection refusedHost offline or SSH not runningStart the remote machine
Connection timed outWrong IP or network issueUpdate IP with connecto update-ip
Permission deniedKey not in authorized_keysRe-pair with connecto pair
Host key verification failedRemote host changedRemove from ~/.ssh/known_hosts
CommandDescription
connecto hostsList all paired hosts
connecto update-ipUpdate host’s IP address
connecto pairRe-establish pairing

update-ip

Update the IP address for a paired host.

Usage

connecto update-ip <HOST> <IP>

Arguments

ArgumentDescription
HOSTName of the paired host
IPNew IP address

Description

The update-ip command changes the IP address for a paired host in ~/.ssh/config. This is useful when:

  • A device gets a new DHCP lease
  • You’re switching between networks (home/office)
  • The VPN assigns a different IP

The SSH keys remain valid - only the IP changes.

update-ip only modifies entries marked # Added by connecto. Hand-written host blocks are never rewritten, even if they share the same alias.

Example

connecto update-ip mydesktop 10.0.2.50

Output:

✓ Updated 'mydesktop' IP: 192.168.1.55 → 10.0.2.50

Finding the New IP

On the Remote Machine

# Linux/macOS
ip addr show | grep inet

# Windows
ipconfig

Using Connecto scan

If the remote is running connecto listen:

connecto scan

The scan results show the current IP.

Notes

  • The SSH keys are not affected
  • You don’t need to re-pair after updating the IP
  • Consider using static IPs or hostnames for frequently-changing devices

Exit status

update-ip exits non-zero if the host is not found among the connecto-managed entries (or no SSH config exists).

CommandDescription
connecto hostsView current IP addresses
connecto testVerify connection after update

export / import

Backup and restore paired hosts configuration.

Export

Usage

connecto export [-o <FILE>]

Options

OptionDescription
-o, --output <FILE>Output file path (prints to stdout if omitted)

Description

Exports all connecto-managed hosts (plus saved subnets) to JSON for backup or transfer to another machine.

Examples

Export to file:

connecto export -o ~/connecto-backup.json

Export to stdout:

connecto export

Pipe to clipboard (macOS):

connecto export | pbcopy

Export format

{
  "version": 1,
  "hosts": [
    {
      "host": "mydesktop",
      "hostname": "192.168.1.55",
      "user": "john",
      "identity_file": "/home/user/.ssh/connecto_mydesktop"
    }
  ],
  "subnets": ["10.0.2.0/24", "10.0.3.0/24"]
}

Note: The export contains SSH config entries only, not the actual key files. To fully backup/restore, you should also copy the key files from ~/.ssh/.


Import

Usage

connecto import <FILE>

Arguments

ArgumentDescription
FILEPath to the export JSON file

Description

Imports paired hosts from a previously exported JSON file. This:

  1. Adds connecto-managed entries to ~/.ssh/config
  2. Restores saved subnets to the config

It does not restore key files — copy those separately (see the export notes above). Files with an unsupported version are rejected.

Example

connecto import ~/connecto-backup.json

Output:

✓ Imported 2 host(s) to SSH config.
✓ Imported 2 subnet(s) to config.

Handling conflicts

Hosts whose alias already exists among the connecto-managed entries are skipped (exact alias match), so importing the same file twice never creates duplicates:

→ All hosts already exist in SSH config.

To replace an existing host, first unpair it:

connecto unpair mydesktop
connecto import backup.json

Use cases

Backup before reinstall

connecto export -o ~/Dropbox/connecto-backup.json
# Reinstall OS
connecto import ~/Dropbox/connecto-backup.json

Transfer to new machine

# On old machine
connecto export -o /tmp/connecto.json
scp /tmp/connecto.json newmachine:/tmp/

# On new machine
connecto import /tmp/connecto.json

Sync across machines

While not a true sync, you can share exports via cloud storage:

# Machine A
connecto export -o ~/Dropbox/connecto.json

# Machine B
connecto import ~/Dropbox/connecto.json

Security notes

  • The export contains references to private keys (file paths), not the keys themselves
  • The actual key files in ~/.ssh/ should be backed up separately
  • For a complete backup, also copy the key files:
# Full backup
connecto export -o connecto-backup.json
cp ~/.ssh/connecto_* ~/backup/
CommandDescription
connecto hostsList current pairings
connecto config listList saved subnets

config

Manage Connecto configuration.

Usage

connecto config <SUBCOMMAND>

Subcommands

SubcommandDescription
add-subnet <CIDR>Add a subnet to scan automatically
remove-subnet <CIDR>Remove a saved subnet
set-default-key <PATH>Set default SSH key for pairing
clear-default-keyClear the default SSH key
listList all configuration
pathShow config file location

add-subnet

Add a subnet that will be scanned automatically.

connecto config add-subnet 10.0.2.0/24

Output:

✓ Added subnet: 10.0.2.0/24

Useful for VPN networks where mDNS doesn’t work across subnets.


remove-subnet

Remove a previously saved subnet.

connecto config remove-subnet 10.0.2.0/24

Output:

✓ Removed subnet: 10.0.2.0/24

If the subnet was not in the config, the command says so and exits non-zero.


set-default-key

Set a default SSH key to use for all pairings (connecto pair and connecto sync).

connecto config set-default-key ~/.ssh/id_ed25519

Output:

✓ Default key set: /Users/john/.ssh/id_ed25519
  → All future pairings will use this key.

Both the private key and its .pub file must exist; otherwise the command fails with a non-zero exit status.

This is useful when you want to:

  • Reuse your existing SSH key across all devices
  • Use a single key for easier management
  • Avoid generating multiple Connecto-specific keys

Note: connecto unpair never deletes keys that don’t follow the connecto_* naming convention, so your personal default key is safe.


clear-default-key

Remove the default SSH key setting.

connecto config clear-default-key

Output:

✓ Default key cleared.
  → Pairings will generate new keys again.

list

Show all configuration (saved subnets and the default key, if set).

connecto config list

Output:

Configured subnets:
  • 10.0.2.0/24
  • 10.0.3.0/24
  • 192.168.100.0/24

Default SSH key:
  • /Users/john/.ssh/id_ed25519

path

Show where the config file is stored.

connecto config path

Output:

/Users/john/Library/Application Support/com.connecto.connecto/config.json

Config file locations

PlatformPath
macOS~/Library/Application Support/com.connecto.connecto/config.json
Linux~/.config/connecto/config.json
Windows%APPDATA%\connecto\connecto\config\config.json

Config file format

The config file is JSON:

{
  "subnets": [
    "10.0.2.0/24",
    "10.0.3.0/24"
  ],
  "default_key": "/Users/john/.ssh/id_ed25519"
}

You can edit it manually, but using the connecto config commands is recommended.


Use cases

VPN Setup

When connecting to machines on a VPN:

# Save the VPN subnet once
connecto config add-subnet 10.0.2.0/24

# Now scans include that subnet automatically
connecto scan

Multiple office networks

connecto config add-subnet 10.0.1.0/24   # Office A
connecto config add-subnet 10.0.2.0/24   # Office B
connecto config add-subnet 192.168.0.0/24 # Home

Scans will check all saved subnets regardless of which network you’re on.

CommandDescription
connecto scanScan for devices
connecto scan --subnetOne-time subnet scan

keys

Manage SSH keys.

CLI key management

List authorized keys

List the keys in this machine’s ~/.ssh/authorized_keys (the keys that are allowed to SSH in):

connecto keys          # same as 'connecto keys list'
connecto keys list

Output:

  AUTHORIZED KEYS

2 authorized key(s) found:

[1] ssh-ed25519 AAAAC3NzaC...IGOCspRomTx alice@laptop
[2] ssh-ed25519 AAAAC3NzaC...w6E5SY8nThb bob@desktop

To remove a key: connecto keys remove <number>

Remove an authorized key

Remove a key by its number from the list, or by a search pattern matched against the key’s comment/type:

connecto keys remove 2
connecto keys remove alice@laptop

You are shown the key and asked to confirm before it is removed. If a pattern matches multiple keys, the matches are listed and nothing is removed — be more specific.

This revokes that device’s SSH access to this machine.

Generate a key pair

connecto keygen [OPTIONS]
OptionDescription
-n, --name <NAME>Key file name in ~/.ssh/ (default: connecto_key)
-c, --comment <TEXT>Key comment (default: user@hostname)
--rsaGenerate RSA-4096 instead of Ed25519

keygen always generates a fresh key — a configured default key (connecto config set-default-key) does not apply here.

connecto keygen --name connecto_work --comment "work laptop"

GUI key management

The Connecto GUI provides a key management interface in the Keys tab:

Authorized keys

View and manage SSH keys that are authorized to connect to this machine. You can:

  • View key algorithm, fingerprint, and comment
  • Remove keys to revoke access

Local keys

View and manage SSH key pairs stored in ~/.ssh/:

  • List keys: See all local key pairs with algorithm, comment, and fingerprint
  • Copy path: Copy the public key path to clipboard
  • Rename: Rename key files (both private and public)
  • Delete: Remove key pairs permanently

Generate new key

Create new SSH key pairs:

  • Choose between Ed25519 (default) and RSA-4096
  • Set custom key name and comment
  • Keys are saved to ~/.ssh/

Useful shell commands

List Connecto-generated key files

ls -la ~/.ssh/connecto_*

View key fingerprint

ssh-keygen -lf ~/.ssh/connecto_mydesktop.pub

Key rotation

  1. Unpair the host: connecto unpair mydesktop
  2. Re-pair: connecto scan && connecto pair 0
CommandDescription
connecto hostsList paired hosts
connecto unpairRemove pairing
connecto pairEstablish new pairing

completions

Generate shell completion scripts.

Usage

connecto completions <SHELL>

Arguments

ArgumentDescription
SHELLTarget shell: bash, zsh, fish, or powershell

Description

Generates tab-completion scripts for your shell. After installation, pressing Tab will complete Connecto commands and options.

Installation

Bash

# Add to ~/.bashrc
connecto completions bash >> ~/.bashrc

# Or install system-wide
sudo connecto completions bash > /etc/bash_completion.d/connecto

Restart your shell or run:

source ~/.bashrc

Zsh

# Add to ~/.zshrc
connecto completions zsh >> ~/.zshrc

Or for Oh My Zsh:

connecto completions zsh > ~/.oh-my-zsh/completions/_connecto

Restart your shell or run:

source ~/.zshrc

Fish

connecto completions fish > ~/.config/fish/completions/connecto.fish

Completions are available immediately in new shells.

PowerShell

# Add to your profile
connecto completions powershell >> $PROFILE

# Reload profile
. $PROFILE

To find your profile path:

echo $PROFILE

Example usage

After installation:

connecto <Tab>
# Shows: completions  config  export  hosts  import  keygen  keys  listen
#        pair  scan  ssh  sync  test  unpair  update-ip

connecto config <Tab>
# Shows: add-subnet  clear-default-key  list  path  remove-subnet  set-default-key

connecto scan --<Tab>
# Shows: --bluetooth  --subnet  --timeout

Troubleshooting

Bash completions not working

Ensure bash-completion is installed:

# macOS
brew install bash-completion

# Ubuntu/Debian
apt install bash-completion

Zsh completions not working

Ensure completion system is initialized. Add to ~/.zshrc:

autoload -Uz compinit && compinit

Fish completions not working

Check that the completions directory exists:

mkdir -p ~/.config/fish/completions

Configuration

Connecto stores configuration in platform-specific locations.

Config file location

PlatformPath
macOS~/Library/Application Support/com.connecto.connecto/config.json
Linux~/.config/connecto/config.json
Windows%APPDATA%\connecto\connecto\config\config.json

Find your config path:

connecto config path

Config file format

{
  "subnets": [
    "10.0.2.0/24",
    "192.168.100.0/24"
  ],
  "default_key": "/Users/john/.ssh/id_ed25519"
}

Fields

FieldTypeDescription
subnetsstring[]CIDR ranges to scan automatically
default_keystring?Path to default SSH key for pairing and sync (optional)

Device cache

connecto scan caches its results so connecto pair <number> can resolve device numbers. The cache lives in the per-user cache directory:

PlatformPath
macOS~/Library/Caches/com.connecto.connecto/devices.json
Linux~/.cache/connecto/devices.json
Windows%LOCALAPPDATA%\connecto\connecto\cache\devices.json

SSH Configuration

Connecto modifies ~/.ssh/config when pairing or syncing. Each paired host gets a four-line block preceded by a marker comment:

# Added by connecto
Host mydesktop
    HostName 192.168.1.55
    User john
    IdentityFile /home/user/.ssh/connecto_mydesktop

Entry fields

FieldDescription
HostAlias used with ssh command
HostNameIP address or hostname
UserRemote username
IdentityFilePath to private key

The # Added by connecto marker is how Connecto tells its own entries apart from yours: hosts, unpair, update-ip, and export only ever read or modify marked blocks. Hand-written host blocks are never listed, rewritten, or removed — even if they share an alias with a managed entry. Config writes are atomic, so an interrupted write cannot truncate the file.

SSH Keys

Keys are stored in the SSH directory:

PlatformDirectory
macOS/Linux~/.ssh/
Windows%USERPROFILE%\.ssh\

Key files

For each paired host:

  • ~/.ssh/connecto_<device> - Private key (mode 600)
  • ~/.ssh/connecto_<device>.pub - Public key

Keys generated by connecto sync use the connecto_sync_ prefix. <device> is the device name sanitized to a lowercase alias (e.g. My Desktopmy-desktop). When a default key is configured (connecto config set-default-key) or --key is used, no new files are created and the config entry points at the existing key.

Key type

Connecto generates Ed25519 keys by default:

  • Modern elliptic curve cryptography
  • 128-bit security level
  • Small key size (compact authorized_keys)
  • Fast generation and authentication

RSA-4096 is available via --rsa.

Home directory resolution

Connecto resolves your home directory (for ~/.ssh and friends) through one shared strategy: the platform user-directories API first, then the HOME / USERPROFILE environment variables as a fallback. The CLI and core library always agree on where ~/.ssh is.

Ports

PortProtocolPurpose
5353UDPmDNS discovery
8099TCPPairing / sync protocol

authorized_keys format

When accepting a pairing, Connecto appends the received public key to ~/.ssh/authorized_keys exactly as sent:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG... user@laptop

The trailing comment is the key’s own comment — user@hostname by default, or whatever was passed via --comment. Duplicate keys are not added twice, and the file is rewritten atomically. Use connecto keys list / connecto keys remove to manage it.

Protocol

Technical details of the Connecto pairing and sync protocols.

Overview

Connecto runs two related protocols over plain TCP (default port 8099):

  • Pairing (one-directional): connecto pair sends a public key to connecto listen
  • Sync (bidirectional): two connecto sync instances exchange public keys

Both use the same wire format and the same Message set, defined in connecto_core::protocol.

Wire format

Every message is a single JSON object terminated by a newline:

{"type":"<MessageType>", ...fields}\n
  • Messages are serialized with a type tag identifying the variant
  • A single message may be at most 64 KiB including the newline; longer lines are rejected and the connection is dropped
  • Every protocol read is bounded by a 30-second timeout (subnet-scan probes use a shorter 2-second timeout); a peer that goes silent cannot park a connection forever

Version negotiation

ConstantValue
PROTOCOL_VERSION1
MIN_SUPPORTED_VERSION1

A peer is accepted when its advertised version lies in MIN_SUPPORTED_VERSION..=PROTOCOL_VERSION. Each side replies with its own version, so after the hello exchange both sides operate at the minimum of the two versions. Versions outside the supported range are rejected with an Error message (code 1); a newer peer is expected to retry at a version the older side supports.

Pairing flow

┌────────────┐                                ┌────────────┐
│   Client   │                                │  Listener  │
│  (pair)    │                                │  (listen)  │
└─────┬──────┘                                └──────┬─────┘
      │                                              │
      │──── TCP connect (port 8099) ────────────────>│
      │                                              │
      │──── Hello {version, device_name} ───────────>│
      │                                              │  version check
      │<─── HelloAck {version, device_name} ─────────│
      │                                              │
      │──── KeyExchange {public_key, comment} ──────>│
      │                                              │  validate key
      │                                              │  derive fingerprint + code
      │                                              │  approval (--verify)
      │                                              │  install key
      │<─── KeyAccepted {message} ───────────────────│
      │                                              │
      │<─── PairingComplete {ssh_user} ──────────────│
      │                                              │
      ×──────────── Connection closed ───────────────×

On the listening side, the received key is validated and its fingerprint and verification code are derived before anything is installed. With --verify, the operator must approve the request before installation; a rejection sends Error (code 4) and installs nothing. If sending KeyAccepted/PairingComplete fails after the key was installed, the listener rolls the installation back so a half-completed exchange leaves no SSH access behind.

Messages

Hello

Sent by the client to open a pairing handshake.

{"type":"Hello","version":1,"device_name":"my-laptop"}

HelloAck

The listener’s reply. The verification_code field is a legacy v1 field and is always null: a code invented by the listener and sent over the wire proves nothing. The real verification code is derived from key material on both sides (see below).

{"type":"HelloAck","version":1,"device_name":"mydesktop","verification_code":null}

KeyExchange

The client’s SSH public key in OpenSSH format.

{"type":"KeyExchange","public_key":"ssh-ed25519 AAAAC3... user@my-laptop","comment":"user@my-laptop"}

KeyAccepted

Confirms the key was added to authorized_keys.

{"type":"KeyAccepted","message":"Key added to authorized_keys"}

PairingComplete

Carries the username to SSH in as.

{"type":"PairingComplete","ssh_user":"john"}

Error

Sent instead of the normal reply when something goes wrong; the connection is closed afterwards.

{"type":"Error","code":4,"message":"Pairing rejected by user"}
CodeMeaning
1Unsupported protocol version
2Expected Hello (wrong opening message)
3Expected KeyExchange (wrong follow-up message)
4Pairing rejected by the user (--verify prompt declined)
5Invalid public key

Verification code

The 6-digit verification code shown during pairing is derived from the public key itself:

  1. Parse the OpenSSH public key
  2. Compute its SHA-256 fingerprint digest
  3. Interpret the first 4 bytes of the raw digest as a big-endian u32
  4. Reduce modulo 1,000,000 and zero-pad to six digits

Both sides derive the code independently: the pairing side from the key it sent, the listening side from the key it received. The codes match only if the same key crossed the wire, so a man-in-the-middle that substitutes its own key changes the code displayed on the listening side. With connecto listen --verify, installation is gated on the operator confirming this code; without it the code is printed but not enforced (see Security).

Sync flow

Sync is bidirectional: both devices run connecto sync, advertise on a dedicated mDNS service type, browse for each other, and exchange keys over a single connection.

┌────────────┐                                       ┌────────────┐
│ Initiator  │                                       │ Responder  │
└─────┬──────┘                                       └──────┬─────┘
      │                                                     │
      │── SyncHello {version, device_name,                  │
      │      initiator_priority, public_key,                │
      │      key_comment, ssh_user} ──────────────────────>│
      │                                                     │ version check
      │                                                     │ arbitration
      │<─ SyncHelloAck {version, device_name, public_key,   │
      │      key_comment, ssh_user, accept_sync} ──────────│
      │                                                     │
      │── SyncComplete {success:true} ────────────────────>│
      │                                                     │ install initiator's key
      │<─ SyncComplete {success:true} ─────────────────────│
      │                                                     │
      │ install responder's key                             │
      ×──────────────── Connection closed ──────────────────×

Key properties:

  • Priority arbitration: each run generates a random u64 priority. The responder accepts an incoming SyncHello only if the initiator’s (priority, device name) pair strictly outranks its own; otherwise it replies accept_sync: false and keeps listening, because its own initiator role outranks the peer’s and will be accepted on the other side. Exactly one direction wins, so running connecto sync on both devices simultaneously converges instead of deadlocking.
  • Self-identification: each device publishes its per-run priority as an mDNS TXT property (priority), and skips advertisements that match both its own device name and its own priority — so a device never tries to sync with itself, while two distinct devices that happen to share a name still find each other.
  • Install after confirmation: neither side installs the peer’s key until the protocol confirms the exchange. The responder installs only after the initiator’s SyncComplete; the initiator installs only after the responder’s SyncComplete. If the responder’s key installation fails, it reports SyncComplete {success: false} so the initiator does not install either; if the responder’s confirmation cannot be sent after it installed, it rolls the installation back.

Discovery

mDNS

ProtocolService typeTXT records
Pairing_connecto._tcp.local.none
Sync_connecto-sync._tcp.local.priority=<random u64>

Devices respond to mDNS queries on UDP port 5353.

Subnet scanning

For cross-subnet discovery (VPNs, mDNS-blocking networks), Connecto scans IP ranges directly:

  1. Generate the list of IPs from CIDR (e.g., 10.0.2.0/24 → 254 IPs); local 10.x.x.x addresses are widened to a /22
  2. Attempt a TCP connection to port 8099 on each IP
  3. Up to 100 concurrent connections, 500ms connect timeout each
  4. Each open port is probed with a real HelloHelloAck exchange (2-second read timeout), so only actual Connecto listeners are reported

A probe stops after HelloAck — it never sends a key — so being scanned cannot pair anything. The one-shot listener keeps accepting connections until a pairing actually completes, so probes do not consume the session.

Security considerations

See Security for the full trust model. In short:

  • The pairing channel is plaintext TCP — but only public keys cross it; private keys never leave the machine that generated them
  • Without --verify, a listener auto-accepts any peer that completes the handshake; with --verify, installation is gated on a code derived from the received key material
  • mDNS device names are not authenticated; verify the code, not the name

Security

Security model and best practices for Connecto.

Trust model — read this first

Connecto’s pairing protocol runs over plaintext TCP on your local network. That is safe for the secrecy of your keys — only public keys ever cross the wire, and private keys never leave the machine that generated them — but it means the protocol itself cannot tell you who is on the other end of the connection.

The important consequence:

Without --verify, connecto listen installs a key from any device on the network that completes the handshake. The received key’s fingerprint and verification code are printed so you can audit what was installed, but nothing is gated on them — by the time you read them, the key is already in authorized_keys.

If anyone untrusted can reach your machine on the pairing port (an office LAN, a shared apartment network, a coffee shop), run the listener with verification:

connecto listen --verify

With --verify, each pairing request shows the requesting device’s name, the received key’s SHA-256 fingerprint, and a 6-digit verification code, and nothing is installed until you approve it.

Why the verification code works

The code is derived from the key material itself: the first 4 bytes of the public key’s SHA-256 fingerprint digest, reduced to 6 digits (see Protocol). Each side computes it independently — the pairing side from the key it sent, the listening side from the key it received. They are never sent over the wire.

A man-in-the-middle that substitutes its own key therefore changes the code shown on the listening side. If the codes on the two screens match, the key you approved is the key the other device actually holds.

What the code does not do: it does not authenticate the device name (mDNS names are unauthenticated and trivially spoofable), and it does not help at all if you don’t compare it — which is why --verify exists.

Threat model

Protected against

ThreatProtection
Password guessingSSH key authentication only
Private key theft in transitPrivate keys never leave the device
Key substitution (MITM) during pairing--verify code is bound to the received key — only with --verify
Network sniffing of the exchangeOnly public keys are transmitted (safe to expose)
Half-completed exchangesKeys are rolled back if the handshake fails after installation; sync installs only after both sides confirm

Not protected against

ThreatMitigation
Unsolicited pairing from the local networkUse --verify; only run listen when you intend to pair
Spoofed device names in scan resultsNames are cosmetic — verify the code, not the name
Malicious network access after pairingSSH itself protects the session; review authorized_keys
Physical device accessUse full-disk encryption
Compromised endpointsKeep systems updated

Key security

Key generation

  • Algorithm: Ed25519 (elliptic curve), generated locally with the ssh-key crate
  • Security level: 128-bit equivalent
  • Key size: 256-bit private, 256-bit public

Ed25519 advantages:

  • No known practical attacks
  • Resistant to timing attacks
  • Small, fast signatures
  • Widely supported (OpenSSH 6.5+)

When to prefer RSA-4096

While Ed25519 is the default and recommended for most users, RSA-4096 may be preferred in certain scenarios:

ReasonDetails
Legacy compatibilitySystems running OpenSSH < 6.5 (pre-2014) or older embedded devices may not support Ed25519
Hardware security modulesSome older HSMs, smart cards, and hardware tokens only support RSA keys
Compliance requirementsCertain regulatory frameworks (e.g., older FIPS 140-2 configurations, some government standards) may mandate RSA
Conservative cryptographic choiceRSA has 40+ years of cryptanalysis; some organizations prefer battle-tested algorithms
Cross-platform interoperabilityBetter support across legacy SSH implementations, older libraries, and enterprise software

RSA-4096 trade-offs:

  • Slower: key generation, signing, and verification are significantly slower than Ed25519
  • Larger keys: 4096-bit keys vs 256-bit (affects storage and transmission)
  • More complex implementation: higher risk of implementation flaws (padding oracles, timing attacks)

To use RSA-4096 with Connecto, pass --rsa when pairing:

connecto pair --rsa <target>

Key storage

ComponentLocationPermissions
Private key~/.ssh/connecto_*600 (owner read/write)
Public key~/.ssh/connecto_*.pub600 (owner read/write)
Authorized keys~/.ssh/authorized_keys600

Files are written atomically (a crash mid-write cannot truncate authorized_keys or ~/.ssh/config). Public keys contain no secret material, so you may safely share them or chmod 644 them yourself.

Key lifecycle

  1. Generation: created fresh for each pairing, unless you configured an existing key with --key or connecto config set-default-key
  2. Distribution: public key sent to the listener
  3. Storage: private key saved locally, public key in the listener’s authorized_keys
  4. Revocation: connecto unpair removes the connecto-managed config entry and deletes the key files only if they follow the connecto_* naming convention — a personal key configured via --key or set-default-key is never deleted. The public key remains in the remote machine’s authorized_keys until removed there (connecto keys remove).

Network security

Pairing protocol

The pairing protocol is unencrypted. That is a deliberate trade-off:

  • Only public keys are transmitted — there is nothing secret to encrypt
  • Authenticity is the real concern, and it is addressed by the --verify code (which is bound to the key material), not by the transport
  • The default listener exits after one successful pairing, shrinking the window in which unsolicited peers can pair at all

Ports used

PortProtocolPurposeExposure
5353UDPmDNSLocal network
8099TCPPairing / syncLocal network
22TCPSSHConfigurable

Recommendations

  1. Use --verify whenever the network has anyone you don’t trust on it
  2. Firewall: only allow 8099 during pairing
  3. VPN: use a VPN for cross-internet pairing
  4. Monitoring: log authorized_keys changes

Best practices

Before pairing

  • Verify you’re on a trusted network — or use connecto listen --verify
  • Confirm the target IP is correct
  • Ensure the listener is running on the intended machine

During pairing (with --verify)

  • Compare the 6-digit code on both screens before approving
  • Check the device name and key comment look right (but remember only the code is cryptographically meaningful)

After pairing

  • Test the connection: connecto test <host>
  • Verify SSH host key fingerprint on first connect
  • Stop the listener if still running

Ongoing

  • Periodically review ~/.ssh/authorized_keys (connecto keys list)
  • Remove unused pairings: connecto unpair <host>
  • Keep Connecto and SSH updated

Auditing

List paired hosts

connecto hosts

List authorized keys

connecto keys list

or directly:

grep connecto ~/.ssh/authorized_keys

Check key fingerprints

for key in ~/.ssh/connecto_*.pub; do
  echo "=== $key ==="
  ssh-keygen -lf "$key"
done

SSH connection logs

# macOS
log show --predicate 'process == "sshd"' --last 1h

# Linux
journalctl -u sshd --since "1 hour ago"

# Windows
Get-EventLog -LogName Security -InstanceId 4624 |
  Where-Object { $_.Message -like "*ssh*" }

Incident response

Suspected compromise

  1. Immediately: Remove unauthorized keys

    connecto keys list
    connecto keys remove <number>
    
  2. Audit: Check all Connecto pairings

    connecto hosts
    
  3. Revoke: Remove suspicious pairings

    connecto unpair <suspicious-host>
    
  4. Investigate: Check SSH logs for unauthorized access

Key rotation

To rotate keys for a host:

connecto unpair mydesktop
# Have target run: connecto listen --verify
connecto scan
connecto pair 0

Comparison

vs password authentication

AspectPasswordConnecto (SSH keys)
Brute forceVulnerableImmune
Credential reuseCommonImpossible
PhishingPossibleDifficult
Setup complexityLowLow (with Connecto)

vs manual SSH keys

AspectManualConnecto
Key generationManualAutomatic
Key distributionCopy/pasteProtocol
Config setupManualAutomatic
DiscoveryManualmDNS

Troubleshooting

Common issues and solutions.

Discovery issues

“No devices found” during scan

Causes:

  1. Listener not running
  2. Firewall blocking mDNS or TCP
  3. Different subnets (VPN scenario)

Solutions:

  1. Verify listener is running:

    # On target machine
    connecto listen
    
  2. Check firewall:

    # Test mDNS (macOS/Linux)
    dns-sd -B _connecto._tcp
    
    # Test TCP port
    nc -zv <target-ip> 8099
    
  3. For VPN/cross-subnet:

    connecto config add-subnet 10.0.2.0/24
    connecto scan
    

Scan finds device but can’t connect

Causes:

  1. Firewall allows mDNS but blocks TCP
  2. Listener crashed after advertising

Solutions:

  1. Restart listener: connecto listen
  2. Check TCP connectivity: nc -zv <ip> 8099
  3. Review firewall rules for TCP 8099

Pairing issues

“Connection refused”

Causes:

  1. Listener not running
  2. Wrong port
  3. Firewall blocking TCP

Solutions:

# Verify listener is running
ps aux | grep connecto

# Check if port is listening
lsof -i :8099  # macOS/Linux
netstat -an | findstr 8099  # Windows

# Test connection
nc -zv <ip> 8099

“Connection timed out”

Causes:

  1. Wrong IP address
  2. Network routing issue
  3. Firewall dropping packets

Solutions:

# Verify IP is reachable
ping <ip>

# Check route
traceroute <ip>  # macOS/Linux
tracert <ip>     # Windows

“Permission denied” after pairing

Causes:

  1. Key not added to authorized_keys
  2. Wrong username
  3. SSH config issue

Solutions:

# On target machine, verify key was added
grep connecto ~/.ssh/authorized_keys

# Check permissions
ls -la ~/.ssh/
# Should be: authorized_keys 600, .ssh dir 700

# Test with verbose SSH
ssh -v <host>

SSH Issues

“Host key verification failed”

The remote host’s SSH server key changed.

Solutions:

# Remove old key
ssh-keygen -R <ip>

# Connect again (will prompt to accept new key)
ssh <host>

“Too many authentication failures”

SSH agent offering too many keys.

Solutions:

# Connect with specific key only
ssh -o IdentitiesOnly=yes -i ~/.ssh/connecto_<host> <host>

Or add IdentitiesOnly yes to the host’s block in ~/.ssh/config (Connecto does not add it automatically).

Can’t connect after IP change

Solutions:

# Update the IP
connecto update-ip <host> <new-ip>

# Verify
connecto test <host>

Platform-specific issues

macOS

mDNS not working:

# Check mDNS daemon
sudo launchctl list | grep mDNS

# Restart mDNS
sudo killall -HUP mDNSResponder

Firewall prompts:

  • Allow “connecto” in System Preferences → Security & Privacy → Firewall

Windows

Firewall blocking Connecto:

# Add firewall rules
New-NetFirewallRule -DisplayName "Connecto mDNS" -Direction Inbound -Protocol UDP -LocalPort 5353 -Action Allow
New-NetFirewallRule -DisplayName "Connecto TCP" -Direction Inbound -Protocol TCP -LocalPort 8099 -Action Allow

OpenSSH not installed:

# Check if OpenSSH is available
Get-WindowsCapability -Online | ? Name -like 'OpenSSH*'

# Install OpenSSH Client
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0

SSH service not running:

# Start SSH agent
Start-Service ssh-agent
Set-Service ssh-agent -StartupType Automatic

Linux

mDNS/Avahi issues:

# Check Avahi daemon
systemctl status avahi-daemon

# Restart Avahi
sudo systemctl restart avahi-daemon

# Install if missing
sudo apt install avahi-daemon  # Debian/Ubuntu
sudo dnf install avahi         # Fedora

SELinux blocking SSH:

# Check SELinux status
getenforce

# Temporarily disable (for testing)
sudo setenforce 0

# Check audit log
sudo ausearch -m avc -ts recent

Config issues

Config file corrupted

Symptoms: Commands fail with JSON parse errors

Solution:

# Find config location
connecto config path

# Reset config (backup first; use the path printed above)
mv "$(connecto config path)" "$(connecto config path).bak"

SSH config conflicts

Symptoms: SSH uses wrong key or settings

Solution:

# Check for duplicate entries
grep -n "Host <hostname>" ~/.ssh/config

# Remove duplicates, keep Connecto entry
# Or manually merge settings

Getting help

Verbose output

All commands support a global verbose flag that enables debug logging:

connecto -v scan
connecto -v pair 0

Connecto exits non-zero on failure, so you can also check $? (or $LASTEXITCODE on PowerShell) in scripts.

Debug information

Collect for bug reports:

# Version
connecto --version

# Config
connecto config list
connecto config path

# SSH config (remove sensitive info)
grep -A4 "# Added by connecto" ~/.ssh/config

# System info
uname -a  # macOS/Linux
systeminfo | findstr /B /C:"OS"  # Windows

Reporting bugs

Report issues at: github.com/andreisuslov/connecto/issues

Include:

  1. Connecto version
  2. Operating system
  3. Steps to reproduce
  4. Error messages
  5. Relevant config (sanitized)