Pairing
Replace
autoApprove with a real prompt.pairing.autoApprove: true is a development convenience. The policy still caps
it at low risk, so it cannot silently hand out write access, but a shipped app
must show a human the device name and the requested capabilities.
Show the SAS digits and make the user compare them.
req.sas is nine digits, shown identically on both devices. That comparison is
the whole defense against a machine-in-the-middle during pairing. A dialog with
only an “Approve” button throws it away.
Set a policy, not just a hook.
Secrets and identity
Know where your keys are stored.
Do not ship
secrets.allowPlaintextFallback: true.Persist the storage directory.
.crosslink-data/<appId>/. Wiping it unpairs every device and changes the host
fingerprint, so every phone must re-scan. In a container, that directory must be
a volume.
Networking
Pin the port if anything forwards to it.
lan.port rather than a remembered one.
Choose the network mode deliberately.
See Connection Modes.
Check the remote diagnostics rather than trusting the mode.
vpnSuspected is set
when STUN and the router disagree about the public address, which is that
situation. See Remote Access.
Give installed clients a stable address.
remote.publicHost to a dynamic-DNS name, or
pairing.bootstrapUrl to an origin you control.
Bind deliberately.
lan.bind: "all" is required for another device to reach the host, and is what
remote mode sets for you. "loopback" produces no lan endpoint at all —
which is the right answer for a host that is not meant to be reachable.
Operations
Expose device management.
listDevices(), revokeDevice(id), revokeAllDevices() and
setDeviceCaps(id, caps) are the whole surface. A user who loses a phone needs
revokeDevice reachable from your UI, not from a REPL.
Log, with a real logger.
logger defaults to a no-op, so the SDK never writes to your stdout uninvited.
Pass a sink of your own, or consoleLogger() from @crosslink/core, or you
will be debugging a production pairing failure with nothing to read.
Subscribe to
connectivity.status.message is written to be shown to a user as-is.
Rate-limit and cap.
maxDevices is the one that is unlimited unless you set
it.
Services, if you run them
Do not run signaling or relay open.
relayToken / signalingToken (or CROSSLINK_RELAY_TOKEN /
CROSSLINK_SIGNALING_TOKEN) on both the services and the clients.
@crosslink/dev-tokens generates per-machine tokens so the defaults are not
open to anyone who finds the port. Deployment detail in
Self-Hosting.
Neither service can read your traffic — the relay forwards ciphertext and holds
no keys — but an open one is capacity someone else can spend.
Validate everything
Every exposed method has
inputSchema or validate.No
idempotent: true on anything that appends, charges or sends.Related
Security Overview
Invariants the design guarantees
Threat Model
What is and is not defended against
Troubleshooting
When it does not work