Security

Verify CoveLink's security claims

We believe security claims should be independently verifiable. Below are exact steps - using common tools like Wireshark, tcpdump, and a text editor - that let you confirm every claim CoveLink makes about your privacy and data.

No cloud traffic

All data stays on your LAN. No relay servers.

End-to-end encrypted

NaCl box - Curve25519 + XSalsa20-Poly1305.

TOFU key pinning

Key rotation is rejected - prevents MITM after first pair.

Physical proximity required

QR pairing requires seeing the screen. Remote pairing is impossible.

One-time pairing tokens

Tokens expire after 5 minutes and are consumed on first use.

Open source daemon

The Go daemon is auditable. The Android app is auditable.

1 No cloud traffic Verifiable with tcpdump / Wireshark

CoveLink makes no connections to any external server during normal operation. You can confirm this by capturing traffic on the machine running the desktop daemon.

Option A - tcpdump (Linux / macOS)

Run this while CoveLink is active and your phone is connected:

# Capture all traffic that is NOT local - replace eth0 with your interface sudo tcpdump -i eth0 -n 'not (dst net 192.168.0.0/16 or dst net 10.0.0.0/8 or dst net 172.16.0.0/12 or dst net 127.0.0.0/8 or dst net 169.254.0.0/16)'
Expected: No output. All CoveLink traffic targets your local subnet (phone and desktop IPs). If you see any non-local destination, it is not from CoveLink - confirm by filtering by the CoveLink process PID or by stopping CoveLink and checking if the traffic disappears.

Option B - Wireshark display filter

ip.dst != 192.168.0.0/16 and ip.dst != 10.0.0.0/8 and ip.dst != 172.16.0.0/12 and ip.dst != 127.0.0.1
Expected: No packets to non-LAN destinations while CoveLink is running (DNS lookups for fonts.googleapis.com may appear if the web UI is open in a browser - these are cosmetic stylesheet loads, not data flows).

Option C - network-level isolation (strongest test)

Put your desktop and phone on a VLAN with no internet gateway. CoveLink should continue to work perfectly - discovery, pairing, notifications, SMS, clipboard - because it needs only LAN reachability between the two devices.

DNS note: the desktop daemon itself makes no DNS queries. The embedded web UI loads fonts.googleapis.com for Inter. If you replace the <link> tag in the static HTML with a locally bundled font, zero external network access occurs even from the browser.

2 Traffic is encrypted on the wire Verifiable with Wireshark

After the initial HELLO handshake (where public keys are exchanged), every message is NaCl box ciphertext. You can confirm this with a packet capture.

Step 1 - capture traffic on port 52300

sudo tcpdump -i eth0 -w /tmp/covelink.pcap tcp port 52300

Trigger some activity - send a notification on your phone, copy something to clipboard, etc.

Step 2 - open in Wireshark and follow the TCP stream

In Wireshark: right-click any packet → Follow → TCP Stream. You should see:

  1. Two short plaintext JSON lines at the start - the HELLO and HELLO_ACK handshake that exchange Curve25519 public keys.
  2. All subsequent lines are binary-looking base64 strings in the form {"n":"…","d":"…"} - the nonce and ciphertext. There is no readable JSON payload.
Expected: After the 2-line handshake, every line is opaque base64. No notification titles, SMS bodies, clipboard text, or any other payload is readable in plaintext.

Verifying the algorithm from source

The encryption code lives in internal/crypto/crypto.go. The relevant call is:

// Seal encrypts plaintext using NaCl box (Curve25519 + XSalsa20-Poly1305) box.Seal(nonce[:], plaintext, &nonce, &peerPublicKey, &ourPrivateKey)

The nonce is 24 bytes of cryptographically random data generated fresh per message via io.ReadFull(rand.Reader, nonce[:]).


3 TOFU key pinning - MITM prevention Verifiable by inspecting trust.json

On first pairing, the Curve25519 public key of each device is stored permanently. If anything claims to be the same device but presents a different key, the connection is rejected. This prevents a man-in-the-middle attack where someone intercepts the connection after initial pairing.

Where keys are stored

The desktop daemon stores its trust database at:

# Linux / macOS ~/.config/covelink/trust.json # Windows %APPDATA%\covelink\trust.json

Open it and inspect the entries:

{ "devices": [ { "device_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "device_name": "Pixel 9", "public_key": "base64-encoded-32-byte-curve25519-public-key", "added_at": 1714000000 } ] }

Test key-change rejection

  1. Note the public_key value for your phone's entry in trust.json.
  2. Edit the file and change one character of the key (making it a different key).
  3. Attempt to connect the phone. The daemon should log a key-mismatch error and close the connection immediately - the phone will not reach the HELLO_ACK step.
  4. Restore the original key value to re-allow connection.
Expected: Connection is rejected with a "public key mismatch" error in the daemon log (journalctl --user -u covelink on Linux, or the console on macOS/Windows). The phone cannot connect until the correct key is restored.

4 One-time pairing tokens Verifiable by attempting token reuse

The QR code contains a cryptographically random 32-byte token that is valid for one use only and expires after 5 minutes. This prevents a captured QR image from being used to pair a second device.

Test token expiry

  1. Open the Settings tab in the CoveLink web UI and display the pairing QR.
  2. Wait more than 5 minutes without scanning.
  3. Attempt to scan the QR. The pairing should fail with a token-expired error.
  4. Click ↻ Refresh QR to generate a new valid token.

Test token single-use (requires two Android devices)

  1. Scan the QR with one phone - pairing succeeds.
  2. Immediately scan the same QR with a second phone (or re-scan from a screenshot).
  3. The second pairing attempt is rejected - the token has been consumed.
Expected: The second device receives a PAIR_ERROR response. The daemon discards the token on first use, making replay attacks impossible even within the 5-minute validity window.

5 Physical proximity required for pairing Architectural guarantee

There is no remote pairing flow. Pairing requires scanning a QR code that is displayed on the desktop screen. Scanning requires seeing the screen, which requires physical presence.

There is no way to initiate pairing via the network, via a link, via email, or via any other remote channel. The QR token is never transmitted over the network in plain form - it is embedded in the QR image only.

What the QR contains: device ID, device name, IP address, TCP port, Curve25519 public key, and the 32-byte random token. None of these alone allow pairing - the full QR payload must be scanned intact.

If you want to pair a phone that is not near the desktop (e.g., a remote employee's phone), CoveLink cannot do this by design. This is a feature, not a limitation.


6 Audit the source code Open source

Key files to audit in the Go daemon:

Key files in the Android app:

The Windows and macOS Wails app shell (which adds the native tray and paid license check) is closed source. The Go daemon core it wraps is the same open-source code. If you are on Linux you can build the entire stack from source.

7 Summary
Claim How to verify Test difficulty
No cloud traffic tcpdump / Wireshark outbound filter Easy
E2E encrypted Follow TCP stream in Wireshark - no plaintext after handshake Easy
TOFU key pinning Mutate trust.json, confirm connection rejected Medium
Token expires after 5 min Wait 5 min, scan old QR - pairing fails Easy
Token single-use Scan same QR twice - second fails Needs 2 phones
Physical proximity required Architectural - no remote pairing API exists Trivial