- Dockerfile with multi-stage build (Node 22 Alpine, sodium-native) - docker-compose.yml with health check for easy self-hosting - README with setup guide, API reference, and project overview - Architecture docs (data flow, component breakdown, protocol constants) - Security model docs (threat model, crypto primitives, self-hosting checklist) - GitHub Actions CI pipeline (test, typecheck, Docker smoke test, extension builds) - GitHub Actions release pipeline (GHCR push, extension zip artifacts) - CONTRIBUTING.md with dev setup and code style guidelines Co-Authored-By: Paperclip <noreply@paperclip.ing>
116 lines
5.8 KiB
Markdown
116 lines
5.8 KiB
Markdown
# CookieBridge Security Model
|
|
|
|
## Design Principles
|
|
|
|
1. **Zero-knowledge relay** — The server stores and forwards encrypted blobs. It never possesses decryption keys and cannot read cookie data.
|
|
2. **Client-side crypto only** — All encryption, decryption, signing, and key exchange happen in the browser extension or agent client.
|
|
3. **Minimal trust surface** — The server is trusted only for availability and message delivery, not confidentiality.
|
|
|
|
## Cryptographic Primitives
|
|
|
|
| Primitive | Algorithm | Library |
|
|
|-----------|-----------|---------|
|
|
| Symmetric encryption | XChaCha20-Poly1305 (AEAD) | libsodium |
|
|
| Digital signatures | Ed25519 | libsodium |
|
|
| Key exchange | X25519 (ECDH) | libsodium |
|
|
| Key derivation | BLAKE2b (generic hash) | libsodium |
|
|
| Random nonces | 24-byte crypto random | libsodium |
|
|
|
|
All algorithms are from the libsodium suite (`sodium-native` on the server, `libsodium-wrappers-sumo` in the extension).
|
|
|
|
## Identity
|
|
|
|
Each device generates two keypairs at first launch:
|
|
|
|
- **Ed25519** — Signing keypair. The public key (hex) serves as the `deviceId`. Used to sign all WebSocket messages and authenticate via challenge-response.
|
|
- **X25519** — Encryption keypair. Used in ECDH key exchange during pairing to derive a shared secret.
|
|
|
|
Keys are stored in `chrome.storage.local` (extension) and never leave the device except for public key exchange during registration and pairing.
|
|
|
|
## Authentication
|
|
|
|
### Token Auth (HTTP)
|
|
|
|
On device registration, the server issues a random Bearer token. This token authenticates all subsequent HTTP requests. Tokens are stored server-side in memory and associated with the device ID.
|
|
|
|
### Challenge-Response (WebSocket)
|
|
|
|
1. Server sends a random challenge (32 bytes, hex).
|
|
2. Client signs the challenge with its Ed25519 private key.
|
|
3. Server verifies the signature against the registered Ed25519 public key.
|
|
|
|
This proves the client possesses the private key corresponding to the registered device ID.
|
|
|
|
## Pairing Security
|
|
|
|
1. Device A creates a pairing session and generates a 6-digit code.
|
|
2. The code must be communicated out-of-band (e.g., user reads it from one device and types it on another).
|
|
3. Device B submits the code along with its X25519 public key.
|
|
4. The server brokers the exchange — both devices receive each other's X25519 public key.
|
|
5. Each device derives the shared secret locally via X25519 ECDH + BLAKE2b key derivation.
|
|
|
|
**Mitigations:**
|
|
- Pairing sessions expire after 5 minutes (`PAIRING_TTL_MS`).
|
|
- Codes are 6 digits (1M combinations). Rate limiting should be applied in production.
|
|
- The server sees the X25519 public keys but not the derived shared secret.
|
|
|
|
## Encryption
|
|
|
|
Cookie data is encrypted with XChaCha20-Poly1305 before leaving the extension:
|
|
|
|
1. A fresh 24-byte random nonce is generated per message.
|
|
2. The cookie payload is encrypted with the shared secret derived from pairing.
|
|
3. The nonce and ciphertext are sent to the server.
|
|
4. The server stores the encrypted blob without the ability to decrypt it.
|
|
|
|
**AEAD properties:**
|
|
- Confidentiality: only holders of the shared secret can decrypt.
|
|
- Integrity: any tampering with the ciphertext is detected.
|
|
- Nonce uniqueness: random 24-byte nonces provide negligible collision probability.
|
|
|
|
## Message Signing
|
|
|
|
Every WebSocket message is signed with the sender's Ed25519 private key. The recipient verifies the signature against the sender's registered public key before processing. This prevents message forgery even if the server is compromised.
|
|
|
|
## Threat Model
|
|
|
|
### What the server knows
|
|
|
|
- Device IDs (Ed25519 public keys), device names, platform strings.
|
|
- Pairing relationships (which devices are paired).
|
|
- Cookie metadata: domain, cookie name, path (stored in plaintext for query routing).
|
|
- Encrypted cookie values (ciphertext — cannot decrypt).
|
|
- Timing: when cookies are synced, update frequency.
|
|
|
|
### What the server does NOT know
|
|
|
|
- Cookie values (encrypted end-to-end).
|
|
- Shared secrets (derived client-side via ECDH).
|
|
- Private keys (never transmitted).
|
|
|
|
### Threats and Mitigations
|
|
|
|
| Threat | Mitigation |
|
|
|--------|-----------|
|
|
| Server compromise | Zero-knowledge design. Attacker gets encrypted blobs and metadata, but not cookie values. |
|
|
| Man-in-the-middle on pairing | Pairing code is out-of-band. ECDH prevents passive eavesdroppers. Active MITM requires server collusion — mitigated by self-hosting. |
|
|
| Replay attacks | Each message has a unique nonce. Lamport clocks provide causal ordering. |
|
|
| Message forgery | Ed25519 signatures on all WebSocket messages. |
|
|
| Cookie metadata leakage | Domain and cookie names are plaintext on the server. Self-hosting eliminates third-party access to this metadata. |
|
|
| Brute-force pairing code | 6-digit code with 5-minute TTL. Apply rate limiting in production (not yet implemented). |
|
|
|
|
### Current Limitations
|
|
|
|
- **In-memory storage** — Server restart clears all data. No persistence layer.
|
|
- **No rate limiting** — Pairing endpoint and registration are not rate-limited. Deploy behind a reverse proxy with rate limiting for production use.
|
|
- **Plaintext metadata** — Domain names, cookie names, and paths are stored unencrypted for query routing. A future version may support encrypted metadata queries.
|
|
- **No TLS termination** — The relay server is plain HTTP. Run behind a TLS-terminating reverse proxy (nginx, Caddy, Cloudflare Tunnel) for production.
|
|
|
|
## Self-Hosting Security Checklist
|
|
|
|
1. **TLS** — Place the relay behind a reverse proxy with TLS (e.g., Caddy with automatic HTTPS).
|
|
2. **Rate limiting** — Configure rate limiting on `/api/pair`, `/api/devices/register`, and `/ws` endpoints.
|
|
3. **Firewall** — Restrict access to the relay port. Only allow traffic from your devices/networks.
|
|
4. **Updates** — Watch for CookieBridge releases and update promptly.
|
|
5. **Monitoring** — Use the `/health` endpoint for uptime monitoring.
|