- 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>
140 lines
7.0 KiB
Markdown
140 lines
7.0 KiB
Markdown
# CookieBridge Architecture
|
|
|
|
## Overview
|
|
|
|
CookieBridge is a cross-device cookie sync system built on a zero-knowledge relay architecture. The relay server transports and stores encrypted cookie blobs but cannot decrypt them. All cryptographic operations happen client-side.
|
|
|
|
## Components
|
|
|
|
### Relay Server (`src/relay/`)
|
|
|
|
A plain Node.js HTTP + WebSocket server. No frameworks — just `http`, `ws`, and `sodium-native`.
|
|
|
|
- **server.ts** — Routes HTTP requests and upgrades WebSocket connections. Single entry point for all API traffic.
|
|
- **connections.ts** — Manages active WebSocket connections. Maps device IDs to sockets for real-time push.
|
|
- **auth.ts** — Two auth methods:
|
|
- **Token auth**: Bearer tokens issued at device registration. Used for HTTP and WebSocket.
|
|
- **Challenge-response**: Server sends random bytes, client signs with Ed25519. Used for WebSocket upgrade.
|
|
- **store.ts** — In-memory encrypted cookie blob storage. Keyed by `(deviceId, domain, cookieName, path)`. Limit: 10,000 cookies per device.
|
|
- **tokens.ts** — Device and agent registries. Tracks device metadata, pairing relationships, and agent access grants.
|
|
|
|
### Cryptography (`src/crypto/`)
|
|
|
|
All crypto uses libsodium via `sodium-native` (server) or `libsodium-wrappers-sumo` (extension).
|
|
|
|
| Operation | Algorithm | Purpose |
|
|
|-----------|-----------|---------|
|
|
| Encryption | XChaCha20-Poly1305 | Cookie payload encryption (AEAD) |
|
|
| Signing | Ed25519 | Message authentication, device identity |
|
|
| Key exchange | X25519 | Deriving shared secrets between paired devices |
|
|
| Key derivation | BLAKE2b (generic hash) | Deriving encryption keys from ECDH output |
|
|
|
|
### Sync Engine (`src/sync/`)
|
|
|
|
- **envelope.ts** — Wire format for WebSocket messages. Each message is signed and encrypted.
|
|
- **conflict.ts** — Last-writer-wins (LWW) conflict resolution. Uses Lamport clock timestamps. Ties broken by lexicographic device ID comparison.
|
|
|
|
### Browser Extension (`extension/`)
|
|
|
|
Single TypeScript codebase compiled per-browser via esbuild.
|
|
|
|
- **service-worker.ts** — Extension lifecycle, device registration, pairing orchestration, sync triggers.
|
|
- **api-client.ts** — HTTP client for relay server REST API.
|
|
- **connection.ts** — WebSocket manager with auto-reconnect and exponential backoff.
|
|
- **crypto.ts** — Client-side encryption/decryption using libsodium-wrappers-sumo.
|
|
- **sync.ts** — Processes incoming cookie updates, applies them via `chrome.cookies` API.
|
|
- **compat.ts** — Cross-browser abstraction layer for Chrome, Firefox, Edge, Safari API differences.
|
|
|
|
## Data Flow
|
|
|
|
### Device Registration
|
|
|
|
```
|
|
Extension Relay Server
|
|
│ │
|
|
│ POST /api/devices/register │
|
|
│ { deviceId, name, platform, │
|
|
│ encPub } │
|
|
│──────────────────────────────────▶│
|
|
│ │
|
|
│ { token, deviceId, ... } │
|
|
│◀──────────────────────────────────│
|
|
```
|
|
|
|
### Pairing
|
|
|
|
```
|
|
Device A Relay Server Device B
|
|
│ │ │
|
|
│ POST /api/pair │ │
|
|
│ { deviceId, x25519Pub,│ │
|
|
│ pairingCode } │ │
|
|
│──────────────────────▶│ │
|
|
│ │ │
|
|
│ │ POST /api/pair/accept │
|
|
│ │ { deviceId, x25519Pub,│
|
|
│ │ pairingCode } │
|
|
│ │◀──────────────────────│
|
|
│ │ │
|
|
│ { peerX25519PubKey } │ { peerX25519PubKey } │
|
|
│◀──────────────────────│──────────────────────▶│
|
|
│ │ │
|
|
│ derive shared secret │ derive shared secret │
|
|
│ (X25519 ECDH) │ (X25519 ECDH) │
|
|
```
|
|
|
|
### Cookie Sync (WebSocket)
|
|
|
|
```
|
|
Device A Relay Server Device B
|
|
│ │ │
|
|
│ cookie_sync envelope │ │
|
|
│ (signed + encrypted) │ │
|
|
│──────────────────────▶│ │
|
|
│ │ store encrypted blob │
|
|
│ │ │
|
|
│ │ forward envelope │
|
|
│ │──────────────────────▶│
|
|
│ │ │
|
|
│ │ decrypt│
|
|
│ │ apply │
|
|
│ ack │ │
|
|
│◀──────────────────────│◀──────────────────────│
|
|
```
|
|
|
|
### Cookie Sync (HTTP Polling)
|
|
|
|
```
|
|
Device Relay Server
|
|
│ │
|
|
│ POST /api/cookies │
|
|
│ { encrypted blob } │
|
|
│───────────────────────────────▶│
|
|
│ │
|
|
│ GET /api/cookies/updates │
|
|
│ ?since=<timestamp> │
|
|
│───────────────────────────────▶│
|
|
│ │
|
|
│ [ encrypted blobs ] │
|
|
│◀───────────────────────────────│
|
|
```
|
|
|
|
## Storage
|
|
|
|
All storage is **in-memory**. The relay server does not persist data to disk. Restarting the server clears all registrations, pairings, and stored cookies. Devices re-register and re-sync automatically on reconnection.
|
|
|
|
This is intentional for the current version — the server is a transient relay, not a database. Persistent storage may be added in a future milestone.
|
|
|
|
## Protocol Constants
|
|
|
|
| Constant | Value | Purpose |
|
|
|----------|-------|---------|
|
|
| `PROTOCOL_VERSION` | `2.0.0` | Wire protocol version |
|
|
| `MAX_STORED_COOKIES_PER_DEVICE` | 10,000 | Per-device cookie limit |
|
|
| `PAIRING_CODE_LENGTH` | 6 digits | Pairing code size |
|
|
| `PAIRING_TTL_MS` | 5 minutes | Pairing session expiry |
|
|
| `NONCE_BYTES` | 24 | XChaCha20 nonce size |
|
|
| `PING_INTERVAL_MS` | 30 seconds | WebSocket keepalive |
|
|
| `PONG_TIMEOUT_MS` | 10 seconds | Pong deadline |
|
|
| `POLL_INTERVAL_MS` | 5 seconds | HTTP polling default |
|