▄██▀ ▀█ ▄██▀ █▄ ▀██ ██▀ ▄██▀ ▀█ ▄██▀ █▄ █▄ ▄█
▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒ ▒▒ ▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒▒▄▒▒▒
▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒▌ ▒▒ ▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒ ▀ ▒▒
▀██▄ ▄█ ▀██▄ █▀ ▀█▄▀ ▀██▄ ▄█ ▀██▄ █▀ ▄██▄ ▄██▄
Covert communications for private group conversations.
Invite, talk, close the client, and the chat vanishes.
Every message is encrypted with XChaCha20 and signed
with Ed25519. A BLAKE3 fingerprint on each key allows
peers to verify one another. SPQR's manual and epoch
ratchets add forward secrecy, while post-quantum
ML-KEM-768 encapsulation keeps recorded communications
unreadable and secure against future cryptanalysis.
Every message is encrypted with XChaCha20-Poly1305. That is the core cipher. Everything else exists to get a fresh, unique XChaCha20 key to the right people at the right time.
Each participant owns one send chain: a stateful KDFChain that steps
forward on every message via HKDF-SHA-256, producing a unique 32-byte key
and wiping the previous chain key. Message keys are wiped after use.
Past keys are unrecoverable from current state.
Epoch transitions use ML-KEM-768 (FIPS 203). When a ratchet fires, the sender generates a shared seed, KEM-encapsulates it separately for each peer, and broadcasts the result. Every peer derives the same new chain from that seed. The KEM ciphertext travels in-band; the decapsulator's keypair rotates immediately after use.
The group uses a Sender Keys model: one send chain per participant, not one per pair. O(N) state regardless of room size.
Every session also mints a fresh Ed25519 signing keypair on construction.
Identity claims and every broadcast are signed under it. Each peer's
claims form a BLAKE3-chained log: every claim binds the previous payload's
hash, so the server cannot reorder, drop, or substitute a structural event
mid-session without breaking the chain. The session signing public key
derives a fingerprint surface (BLAKE3(sessionPk, 16) → eight OKLCh
swatches + 16-char hex) for out-of-band verification. Both clients expose
a sidebar with two panels: Verify lists your fingerprint and every
peer's side-by-side; Event Log captures every inbound/outbound
WebSocket frame and every crypto action with redacted payloads and
expandable detail rows.
This implements the Sparse Post-Quantum Ratchet from Signal's Double Ratchet spec (§5, Revision 4). For more detail, see PROTOCOL.md.
Cryptographic primitives are provided by leviathan-crypto.
Point chat.example.com at the host you'll run on, then:
docker pull xerostyle/covcom:latest
docker run -d \
-p 80:80 -p 443:443 \
-e DOMAIN=chat.example.com \
xerostyle/covcom:latestOpen https://chat.example.com in a browser. Create a room, share the invite, & chat.
- Bun v1.1 or later
- Docker for the containerized server
- A modern browser (Chrome, Firefox, or Safari) for the web client
git clone https://github.com/xero/covcom
cd covcom
bun iThe Docker image runs the Bun WebSocket server behind Caddy with automatic TLS via ACME. There are no build arguments; all configuration is runtime environment variables.
Pull and run from a registry:
docker pull xerostyle/covcom:latest
docker run -d \
-p 80:80 -p 443:443 \
-e DOMAIN=chat.example.com \
xerostyle/covcom:latestPublished to Docker Hub as
xerostyle/covcom and GHCR
as ghcr.io/xero/covcom. Pin a specific version (e.g. :3.0.0) in production
so a vulnerability disclosure does not silently upgrade you. See
USAGE.md for tag conventions and how to extend the
image.
Build locally:
bun build:dockerRun locally:
DOMAIN=chat.example.com bun run:dockerCaddy provisions a TLS certificate for $DOMAIN on first start. The
container listens on ports 80 and 443.
Stop:
docker compose -f docker/docker-compose.yml downLogs:
docker compose -f docker/docker-compose.yml logs -fFor environments without docker compose. The compose file is the
recommended path; these are escape hatches.
Build:
bun build:docker:rawRun:
DOMAIN=chat.example.com bun run:docker:rawThe raw run forwards DOMAIN, PORT, ADMIN_TOKEN, and MAX_ROOM_SIZE
from the environment and mounts named volumes for Caddy data and config.
Runs the server directly via Bun without TLS or Caddy. Use this when fronting COVCOM with your own reverse proxy.
bun start:serverThis invokes bun run src/index.ts in the server/ workspace and listens
on localhost:$PORT (default 3000).
Runs the server in watch mode, useful for local testing where clients
connect over ws://.
bun dev:serverThe server starts on localhost:3000 and reloads on source changes.
| Variable | Default | Description |
|---|---|---|
DOMAIN |
required | Domain name for Caddy TLS |
PORT |
3000 |
Internal port the Bun server listens on |
ADMIN_TOKEN |
unset | Optional token required to create rooms |
ROOM_TTL |
24 |
Hours of inactivity before an empty room is deleted. 0 disables TTL |
MAX_ROOM_SIZE |
20 |
Maximum participants per room. 0 is unlimited |
ADMIN_TOKEN gates room creation only. Joining is gated by the
roomSecret embedded in the invite, which the server generates at creation
time. You do not need ADMIN_TOKEN set to run a private server; the
roomSecret alone prevents uninvited joins.
Development:
bun dev:webOpen http://localhost:5173.
Static build:
bun build:webProduces web/dist/: an inlined index.html plus a same-origin pool worker
(covcom-pool-worker.js) used for encrypted file transfer. The policy is
strict-CSP friendly — worker-src 'self' with no blob:, so file transfer
works in Safari/WebKit under a strict CSP (see
leviathan-crypto/docs/csp.md).
Serve the directory from any static file host with no build step, or let
bun build:docker bake it into the image.
Preview the production build:
bun run --cwd web previewServes the contents of web/dist/ locally for smoke-testing the bundled
output.
The CLI is a compiled Bun binary with a custom zero-dependency TUI.
Run from source:
bun dev:cliJoin directly from a .room file:
bun dev:cli join /path/to/invite.roomBuild a standalone binary for the current platform:
bun build:cliThe binary lands in cli/dist/.
Build for a specific target:
bun run --cwd cli build:mac-arm # macOS Apple Silicon → cli/dist/covcom-macos-arm64
bun run --cwd cli build:mac-x64 # macOS Intel → cli/dist/covcom-macos-x64
bun run --cwd cli build:linux # Linux x86_64 → cli/dist/covcom-linux-x64
bun run --cwd cli build:win # Windows x86_64 → cli/dist/covcom-win-x64.exeBuild all platforms at once:
bun build:cli:allSettings save to ~/.config/covcom/config.json after a
successful connection. The file is optional; all fields can be set
interactively.
{
"server": "chat.example.com",
"username": "xero",
"copyCmd": "xsel -b",
"showSystem": true,
"theme": {
"btnFocusBg": { "type": "256", "n": 33 },
"yourName": { "type": "hex", "value": "#ff8800" }
}
}copyCmd sets the clipboard binary used on the lobby screen. If unset, the
CLI probes for pbcopy, xclip, xsel, and wl-copy in that order.
theme accepts any subset of the theme type. Each slot takes one of:
{ "type": "ansi16", "n": 0-15 }, { "type": "256", "n": 0-255 }, or
{ "type": "hex", "value": "#rrggbb" }.
| Key | Action |
|---|---|
Tab / Shift+Tab |
Cycle focus |
Enter |
Send message / confirm |
Ctrl+R |
Rotate encryption keys (ratchet step) |
Ctrl+E |
Toggle event-log sidebar |
Ctrl+V |
Toggle fingerprint-verify sidebar |
Ctrl+C |
Quit and wipe session |
When the sidebar has focus, ↑/↓ move selection in the event log, PgUp/PgDn
page through, Enter expands the selected entry's details, and +/- step
the sidebar width by 5%. Esc closes the sidebar.
Files attach via the + button. Type or paste a path and use Tab for
completion. Received files save to the current working directory; existing
filenames get a numeric suffix.
Create a room:
- Enter a server address and a username, then select Create Room.
- The lobby screen shows an armored invite block, a QR code of the same bytes, and copy/download buttons. Share it via any channel.
- The screen waits until a peer joins.
Join a room:
- Enter a username and select Join Room.
- Paste the armored invite text, drag-drop the
.roomfile (web), or provide the file path (CLI). - Select Connect.
Once both sides complete the handshake, the chat opens. The server has relayed a sequence of encrypted blobs and learned nothing about the content.
Late joiners receive current epoch seeds from all present members and enter the session at whatever epoch each sender is at. Messages sent before you joined are not recoverable. This is forward secrecy working as intended.
Deeper references for users, auditors, contributors, and the curious.
| Document | Purpose |
|---|---|
| USAGE | Client and server applications development and runtime help |
| PROTOCOL | Cipher, chains, ratchet, group model, session lifecycle, server role |
| CRYPTOGRAPHY | Primitives, KDF chains, wire format, invite encoding |
| THREAT-MODEL | Principals, adversary tiers, guarantees, non-goals |
| CLI-SPEC | CLI architecture, rendering, input, widgets, views, & color system |
| SECURITY-POLICY | Supported versions, disclosure policy, cryptographic foundation |
| DIAGRAM | Animated visualization of a session: establishment, epochs, and reconnect ceremonies |
Tip
Documentation is available in the repo ./docs folder and published to the project wiki.
Run all unit tests:
bun run testThis runs test:server, test:lib, test:web, and test:cli in sequence
(each via bun run). Note the bun run prefix: a bare bun test invokes
Bun's built-in runner with the script name treated as a path filter, not the
package script.
Run tests for a single package:
bun run test:server # server WebSocket broker
bun run test:lib # shared crypto session layer
bun run test:web # web client (store, session, bridge, views) via happy-dom
bun run test:cli # CLI widgets, key parsing, state machine, event logRun the end-to-end test (Playwright):
bunx playwright install chromium # one time
bun run test:e2etest:e2e auto-starts the Bun broker and the Vite dev server, then drives two
browser contexts through a real two-party encrypted chat (create → invite →
join → exchange messages → verify fingerprints). It is not part of bun run test
because it needs running servers and a browser.
Lint and autofix:
bun fixRepository layout:
server/ WebSocket broker (Bun)
lib/ Shared crypto session layer
web/ Vite + vanilla TS web client
cli/ Custom zero-dependency TUI
docker/ Dockerfile, Caddyfile template, entrypoint
docs/ Project documentation / Wiki sources
COVCOM is released under the MIT license.