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.
All data stays on your LAN. No relay servers.
NaCl box - Curve25519 + XSalsa20-Poly1305.
Key rotation is rejected - prevents MITM after first pair.
QR pairing requires seeing the screen. Remote pairing is impossible.
Tokens expire after 5 minutes and are consumed on first use.
The Go daemon is auditable. The Android app is auditable.
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:
Option B - Wireshark display filter
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.
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.
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
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:
- Two short plaintext JSON lines at the start - the
HELLOandHELLO_ACKhandshake that exchange Curve25519 public keys. - All subsequent lines are binary-looking base64 strings in the form
{"n":"…","d":"…"}- the nonce and ciphertext. There is no readable JSON payload.
Verifying the algorithm from source
The encryption code lives in internal/crypto/crypto.go. The relevant
call is:
The nonce is 24 bytes of cryptographically random data generated fresh per message
via io.ReadFull(rand.Reader, nonce[:]).
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:
Open it and inspect the entries:
Test key-change rejection
- Note the
public_keyvalue for your phone's entry intrust.json. - Edit the file and change one character of the key (making it a different key).
- 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.
- Restore the original key value to re-allow connection.
journalctl --user -u covelink on Linux, or the console on macOS/Windows). The phone cannot connect until the correct key is restored.
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
- Open the Settings tab in the CoveLink web UI and display the pairing QR.
- Wait more than 5 minutes without scanning.
- Attempt to scan the QR. The pairing should fail with a token-expired error.
- Click ↻ Refresh QR to generate a new valid token.
Test token single-use (requires two Android devices)
- Scan the QR with one phone - pairing succeeds.
- Immediately scan the same QR with a second phone (or re-scan from a screenshot).
- The second pairing attempt is rejected - the token has been consumed.
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.
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.
Key files to audit in the Go daemon:
internal/crypto/crypto.go- NaCl box wrapper, key generation, Seal/Openinternal/hub/hub.go- connection management, HELLO handshake, key verification, encryption enableinternal/trust/trust.go- trust store, TOFU logic, key storageinternal/pairing/pairing.go- QR token generation, expiry, single-use enforcementinternal/discovery/discovery.go- UDP multicast only, no external calls
Key files in the Android app:
app/src/main/java/…/crypto/CryptoBox.kt- libsodium / lazysodium NaCl boxapp/src/main/java/…/network/HubConnection.kt- HELLO handshake, encryption enableapp/src/main/java/…/data/AppConfig.kt- keypair generation, storage in EncryptedSharedPreferences
| 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 |