Security & Encryption Architecture
Crosslink is built as a zero-trust transport: with no broker configured, a phone pairs and connects directly to the host over the address in the QR code. When a signaling service, relay, or developer-configured tunnel sits in the path, it only carries metadata or ciphertext. It does not get plaintext, session keys, or private device identity material.Trust boundaries
The important boundary is not “client vs server”. It is “trusted endpoint vs everything else”.- The host owns application identity, capability policy, revocation, and the primary logs.
- The browser client owns user approval and the user-visible pairing check.
- Signaling only helps devices discover one another and route the pairing code.
- Relay only forwards encrypted frames.
Cryptographic primitives
Crosslink uses the following primitives in the current implementation:
The code treats these as implementation choices, not policy knobs. You should not swap them ad hoc in an app, because the rest of the stack expects the transcript, replay protection, and frame encoding to behave exactly the same way.
Pairing trust
Pairing uses two checks that serve different purposes:- The QR pins the host fingerprint and lists the routes the client is allowed to try.
- The SAS digits let a human verify that the screen being scanned is the one the host intended.
Capabilities and revocation
Crosslink is capability-based. A paired device only gets the capabilities the host grants, and those grants can be revoked later. Operationally, that means:- Keep capability names narrow and specific.
- Mark dangerous actions with explicit approval.
- Re-check capability state on every privileged action.
- Treat revocation as immediate, not eventual.
What to log
Crosslink’s logger is structured and redacting by design. Use it for the event name, state transition, and sanitized metadata. Do not log raw request objects, private keys, seed material, auth headers, or full tokens.- endpoint kind and transport selection
- device ids, request ids, and method names
- a redacted reason for a failure
- whether a secret store, keychain, or encrypted-file fallback was used
- raw
Authorizationheaders - pairing codes pasted into logs
- private keys, seeds, passphrases, or one-time tokens
- opaque provider error objects copied to users verbatim
Debugging security-sensitive failures
When a security-sensitive path fails, use the narrowest useful diagnostic:- Check
server.status()on the host for the active network mode, transport reachability, and secret-store backend. - Check the host’s advertised endpoints and confirm the route kind matches the intended mode.
- If the problem is remote access, run
npm run check:remoteornode scripts/check-remote-access.mjs <port>and compare the returned reason with the host logs. - If the problem is pairing, verify the QR fingerprint and SAS first, before retrying with a new code.
- If the problem is authorization, inspect the capability policy and the requested capability, not just the call site.
Security checklist
- Use
consoleLogger()or another logger that preserves structured fields. - Keep secrets in the OS keychain,
safeStorage, or encrypted-file fallback only. - Never echo tokens, seeds, or private keys back to the browser.
- Require explicit approval for risky capabilities.
- Revoke devices instead of rotating the whole app secret when one device is compromised.
- Prefer
https,wss, or localhost for anything that needs browser storage or a service worker. - Treat signaling and relay as untrusted transport helpers, not as identity sources.