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 --verifygates key installation behind a 6-digit code bound to the received key - Bidirectional Sync -
connecto syncon both devices exchanges keys both ways - Auto SSH Config -
ssh hostnamejust 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 ✓
- Target runs
connecto listen- advertises via mDNS - Client runs
connecto scan- discovers available devices - Client runs
connecto pair 0- exchanges SSH keys - Done -
ssh hostnamejust 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 - Install Connecto on your system
- Quick Start - Get up and running in minutes
- VPN Setup - Configure for VPN/cross-subnet use
Installation
Connecto can be installed on macOS, Linux, and Windows.
macOS
Homebrew (Recommended)
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
PowerShell (Recommended)
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
- Download
connecto-windows-x86_64.zipfrom GitHub Releases - Extract to a directory of your choice (e.g.
%LOCALAPPDATA%\connecto) - 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
- Connecto installed on both machines (Installation)
- Both machines on the same network (or VPN setup)
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?
- Listener advertised itself via mDNS on your local network
- Scanner discovered the listener and displayed it
- 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 --verifyinstead — 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
- List paired hosts:
connecto hosts - Test connection:
connecto test mydesktop - Update IP:
connecto update-ip mydesktop 10.0.0.5 - Remove pairing:
connecto unpair mydesktop - Bidirectional sync:
connecto sync
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/24172.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:
- The target is running
connecto listen - Firewall allows TCP 8099
- 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:
- Advertises the device via mDNS on the local network
- Waits for incoming pairing requests on TCP port 8099
- Accepts public keys and adds them to
~/.ssh/authorized_keys - Exits after successful pairing (unless
--continuousis used)
Options
| Option | Description |
|---|---|
-p, --port <PORT> | Port to listen on (default: 8099) |
-n, --name <NAME> | Device name to advertise (default: hostname) |
--verify | Require interactive approval of a verification code before installing a key |
-c, --continuous | Keep listening after successful pairing |
--adhoc | Create an ad-hoc WiFi network (bypasses router, for isolated networks) |
--bluetooth | Enable 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...
With verification (recommended on shared networks)
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
airportutility 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 aniwfallback). - 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
- Client connects and the two sides negotiate a protocol version
- Client sends its public key
- The listener validates the key and derives its fingerprint and 6-digit verification code
- 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. - Listener adds the key to
~/.ssh/authorized_keysand sends back its username - 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--verifyon any network with untrusted devices — see Security. - Only run
listenwhen 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:
- mDNS discovery - finds devices advertising the
_connecto._tcpservice - Subnet scanning - scans your local subnets, saved subnets, and any
--subnetarguments - Ad-hoc network scan - looks for Connecto ad-hoc WiFi networks (created with
connecto listen --adhoc) - Bluetooth LE - with
--bluetooth, scans for BLE-advertised devices (requires a build with thebluetoothfeature)
Options
| Option | Description |
|---|---|
-t, --timeout <SECONDS> | Scan timeout in seconds (default: 5) |
-s, --subnet <CIDR> | Additional subnet to scan (can be repeated) |
--bluetooth | Enable 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 Size | IPs | Approximate Time |
|---|---|---|
| /24 | 254 | 2-3 seconds |
| /22 | 1,022 | 5-10 seconds |
| /16 | 65,534 | Not recommended |
Connecto scans up to 100 IPs concurrently with a 500ms timeout per IP.
No devices found?
If no devices are found:
- Ensure the target is running
connecto listen - Check firewall allows TCP 8099 and UDP 5353
- For VPN, add the remote subnet:
connecto config add-subnet <CIDR> - 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
| Argument | Description |
|---|---|
TARGET | Device number from scan, or direct IP:port |
Options
| Option | Description |
|---|---|
-k, --key <PATH> | Use existing SSH key instead of generating new |
-c, --comment <TEXT> | Custom key comment |
--rsa | Generate RSA-4096 instead of Ed25519 |
Description
The pair command establishes SSH key-based authentication with a remote device:
- Generates a new Ed25519 SSH key pair (or uses an existing key — see below)
- Sends the public key to the target device
- Displays a 6-digit verification code to compare with the listening device
- Saves the private key to
~/.ssh/connecto_<device> - Updates
~/.ssh/configfor easyssh hostnameaccess
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 Desktop → my-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:
- The SSH config block is replaced in place
- 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:
--keyflag (if specified)- Config default key (if set)
- 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:
- Advertise via mDNS (
_connecto-sync._tcp.local.) - Scan for sync peers on the network
- When found, exchange SSH public keys
- Both add each other’s key to
~/.ssh/authorized_keys(only after both sides confirm the exchange) - Both can now SSH to each other
Options
| Option | Description |
|---|---|
-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) |
--rsa | Use 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
- 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)
- Both devices search: each device also searches for other sync services
- Priority arbitration picks one direction: whichever device connects
sends
SyncHellowith 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 startingconnecto syncon both devices at the same time converges instead of hanging. - Key exchange: the winning initiator’s
SyncHellocarries its public key; the responder replies withSyncHelloAckcontaining its own key - Confirm, then install: both sides exchange
SyncCompleteand only install the peer’s key after the other side has confirmed — an aborted exchange leaves no key behind - SSH config: each side writes a connecto-managed
~/.ssh/configentry for the peer
See Protocol for the exact message flow.
Comparison with listen + pair
| Aspect | listen + pair | sync |
|---|---|---|
| Direction | One-way | Bidirectional |
| Workflow | Run listen on target, pair on client | Run sync on both |
| Result | Client can SSH to target | Both can SSH to each other |
| Use case | Setting up access to a server | Two 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
~/.sshdirectory 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
--keyor 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
| Field | Description |
|---|---|
| Host alias | Name to use with the ssh command |
| User | Username for SSH connection |
| Address | IP address or hostname of the remote machine |
Related commands
| Command | Description |
|---|---|
connecto test <host> | Test SSH connection |
connecto update-ip <host> <ip> | Update host’s IP address |
connecto unpair <host> | Remove pairing |
connecto export | Backup all pairings |
unpair
Remove a paired host and delete its connecto-generated keys.
Usage
connecto unpair <HOST>
Arguments
| Argument | Description |
|---|---|
HOST | Name of the paired host to remove |
Description
The unpair command removes a pairing established by Connecto:
- Removes the connecto-managed host entry from
~/.ssh/config - 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 removeon 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.
Related commands
| Command | Description |
|---|---|
connecto hosts | List all paired hosts |
connecto export | Backup pairings before removing |
test
Test SSH connection to a paired host.
Usage
connecto test <HOST>
Arguments
| Argument | Description |
|---|---|
HOST | Name of the paired host to test |
Description
The test command verifies that SSH connectivity works to a paired host. It:
- Runs
sshagainst the host alias (so it uses your~/.ssh/configentry) - Uses
BatchMode(no password prompts) and a 5-second connection timeout - Executes a trivial echo command and checks the response
- 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
| Error | Cause | Solution |
|---|---|---|
| Connection refused | Host offline or SSH not running | Start the remote machine |
| Connection timed out | Wrong IP or network issue | Update IP with connecto update-ip |
| Permission denied | Key not in authorized_keys | Re-pair with connecto pair |
| Host key verification failed | Remote host changed | Remove from ~/.ssh/known_hosts |
Related commands
| Command | Description |
|---|---|
connecto hosts | List all paired hosts |
connecto update-ip | Update host’s IP address |
connecto pair | Re-establish pairing |
update-ip
Update the IP address for a paired host.
Usage
connecto update-ip <HOST> <IP>
Arguments
| Argument | Description |
|---|---|
HOST | Name of the paired host |
IP | New 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).
Related commands
| Command | Description |
|---|---|
connecto hosts | View current IP addresses |
connecto test | Verify connection after update |
export / import
Backup and restore paired hosts configuration.
Export
Usage
connecto export [-o <FILE>]
Options
| Option | Description |
|---|---|
-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
| Argument | Description |
|---|---|
FILE | Path to the export JSON file |
Description
Imports paired hosts from a previously exported JSON file. This:
- Adds connecto-managed entries to
~/.ssh/config - 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/
Related commands
| Command | Description |
|---|---|
connecto hosts | List current pairings |
connecto config list | List saved subnets |
config
Manage Connecto configuration.
Usage
connecto config <SUBCOMMAND>
Subcommands
| Subcommand | Description |
|---|---|
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-key | Clear the default SSH key |
list | List all configuration |
path | Show 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
| Platform | Path |
|---|---|
| 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.
Related commands
| Command | Description |
|---|---|
connecto scan | Scan for devices |
connecto scan --subnet | One-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]
| Option | Description |
|---|---|
-n, --name <NAME> | Key file name in ~/.ssh/ (default: connecto_key) |
-c, --comment <TEXT> | Key comment (default: user@hostname) |
--rsa | Generate 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
- Unpair the host:
connecto unpair mydesktop - Re-pair:
connecto scan && connecto pair 0
Related commands
| Command | Description |
|---|---|
connecto hosts | List paired hosts |
connecto unpair | Remove pairing |
connecto pair | Establish new pairing |
completions
Generate shell completion scripts.
Usage
connecto completions <SHELL>
Arguments
| Argument | Description |
|---|---|
SHELL | Target 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
| Platform | Path |
|---|---|
| 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
| Field | Type | Description |
|---|---|---|
subnets | string[] | CIDR ranges to scan automatically |
default_key | string? | 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:
| Platform | Path |
|---|---|
| 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
| Field | Description |
|---|---|
Host | Alias used with ssh command |
HostName | IP address or hostname |
User | Remote username |
IdentityFile | Path 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:
| Platform | Directory |
|---|---|
| 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 Desktop →
my-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
| Port | Protocol | Purpose |
|---|---|---|
| 5353 | UDP | mDNS discovery |
| 8099 | TCP | Pairing / 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 pairsends a public key toconnecto listen - Sync (bidirectional): two
connecto syncinstances 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
typetag 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
| Constant | Value |
|---|---|
PROTOCOL_VERSION | 1 |
MIN_SUPPORTED_VERSION | 1 |
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"}
| Code | Meaning |
|---|---|
| 1 | Unsupported protocol version |
| 2 | Expected Hello (wrong opening message) |
| 3 | Expected KeyExchange (wrong follow-up message) |
| 4 | Pairing rejected by the user (--verify prompt declined) |
| 5 | Invalid public key |
Verification code
The 6-digit verification code shown during pairing is derived from the public key itself:
- Parse the OpenSSH public key
- Compute its SHA-256 fingerprint digest
- Interpret the first 4 bytes of the raw digest as a big-endian
u32 - 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
u64priority. The responder accepts an incomingSyncHelloonly if the initiator’s(priority, device name)pair strictly outranks its own; otherwise it repliesaccept_sync: falseand keeps listening, because its own initiator role outranks the peer’s and will be accepted on the other side. Exactly one direction wins, so runningconnecto syncon 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’sSyncComplete. If the responder’s key installation fails, it reportsSyncComplete {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
| Protocol | Service type | TXT 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:
- Generate the list of IPs from CIDR (e.g.,
10.0.2.0/24→ 254 IPs); local10.x.x.xaddresses are widened to a /22 - Attempt a TCP connection to port 8099 on each IP
- Up to 100 concurrent connections, 500ms connect timeout each
- Each open port is probed with a real
Hello→HelloAckexchange (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 listeninstalls 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 inauthorized_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
| Threat | Protection |
|---|---|
| Password guessing | SSH key authentication only |
| Private key theft in transit | Private 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 exchange | Only public keys are transmitted (safe to expose) |
| Half-completed exchanges | Keys are rolled back if the handshake fails after installation; sync installs only after both sides confirm |
Not protected against
| Threat | Mitigation |
|---|---|
| Unsolicited pairing from the local network | Use --verify; only run listen when you intend to pair |
| Spoofed device names in scan results | Names are cosmetic — verify the code, not the name |
| Malicious network access after pairing | SSH itself protects the session; review authorized_keys |
| Physical device access | Use full-disk encryption |
| Compromised endpoints | Keep systems updated |
Key security
Key generation
- Algorithm: Ed25519 (elliptic curve), generated locally with the
ssh-keycrate - 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:
| Reason | Details |
|---|---|
| Legacy compatibility | Systems running OpenSSH < 6.5 (pre-2014) or older embedded devices may not support Ed25519 |
| Hardware security modules | Some older HSMs, smart cards, and hardware tokens only support RSA keys |
| Compliance requirements | Certain regulatory frameworks (e.g., older FIPS 140-2 configurations, some government standards) may mandate RSA |
| Conservative cryptographic choice | RSA has 40+ years of cryptanalysis; some organizations prefer battle-tested algorithms |
| Cross-platform interoperability | Better 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
| Component | Location | Permissions |
|---|---|---|
| Private key | ~/.ssh/connecto_* | 600 (owner read/write) |
| Public key | ~/.ssh/connecto_*.pub | 600 (owner read/write) |
| Authorized keys | ~/.ssh/authorized_keys | 600 |
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
- Generation: created fresh for each pairing, unless you configured an
existing key with
--keyorconnecto config set-default-key - Distribution: public key sent to the listener
- Storage: private key saved locally, public key in the listener’s
authorized_keys - Revocation:
connecto unpairremoves the connecto-managed config entry and deletes the key files only if they follow theconnecto_*naming convention — a personal key configured via--keyorset-default-keyis never deleted. The public key remains in the remote machine’sauthorized_keysuntil 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
--verifycode (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
| Port | Protocol | Purpose | Exposure |
|---|---|---|---|
| 5353 | UDP | mDNS | Local network |
| 8099 | TCP | Pairing / sync | Local network |
| 22 | TCP | SSH | Configurable |
Recommendations
- Use
--verifywhenever the network has anyone you don’t trust on it - Firewall: only allow 8099 during pairing
- VPN: use a VPN for cross-internet pairing
- Monitoring: log
authorized_keyschanges
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
-
Immediately: Remove unauthorized keys
connecto keys list connecto keys remove <number> -
Audit: Check all Connecto pairings
connecto hosts -
Revoke: Remove suspicious pairings
connecto unpair <suspicious-host> -
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
| Aspect | Password | Connecto (SSH keys) |
|---|---|---|
| Brute force | Vulnerable | Immune |
| Credential reuse | Common | Impossible |
| Phishing | Possible | Difficult |
| Setup complexity | Low | Low (with Connecto) |
vs manual SSH keys
| Aspect | Manual | Connecto |
|---|---|---|
| Key generation | Manual | Automatic |
| Key distribution | Copy/paste | Protocol |
| Config setup | Manual | Automatic |
| Discovery | Manual | mDNS |
Troubleshooting
Common issues and solutions.
Discovery issues
“No devices found” during scan
Causes:
- Listener not running
- Firewall blocking mDNS or TCP
- Different subnets (VPN scenario)
Solutions:
-
Verify listener is running:
# On target machine connecto listen -
Check firewall:
# Test mDNS (macOS/Linux) dns-sd -B _connecto._tcp # Test TCP port nc -zv <target-ip> 8099 -
For VPN/cross-subnet:
connecto config add-subnet 10.0.2.0/24 connecto scan
Scan finds device but can’t connect
Causes:
- Firewall allows mDNS but blocks TCP
- Listener crashed after advertising
Solutions:
- Restart listener:
connecto listen - Check TCP connectivity:
nc -zv <ip> 8099 - Review firewall rules for TCP 8099
Pairing issues
“Connection refused”
Causes:
- Listener not running
- Wrong port
- 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:
- Wrong IP address
- Network routing issue
- Firewall dropping packets
Solutions:
# Verify IP is reachable
ping <ip>
# Check route
traceroute <ip> # macOS/Linux
tracert <ip> # Windows
“Permission denied” after pairing
Causes:
- Key not added to authorized_keys
- Wrong username
- 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:
- Connecto version
- Operating system
- Steps to reproduce
- Error messages
- Relevant config (sanitized)