▄██▀ ▀█ ▄██▀ █▄ ▀██ ██▀ ▄██▀ ▀█ ▄██▀ █▄ █▄ ▄█
▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒ ▒▒ ▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒▒▄▒▒▒
▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒▌ ▒▒ ▐▒▒▒ ▐▒▒▒ ▒▒▌ ▒▒ ▀ ▒▒
▀██▄ ▄█ ▀██▄ █▀ ▀█▄▀ ▀██▄ ▄█ ▀██▄ █▀ ▄██▄ ▄██▄
XChaCha20 · ML-KEM-768 · SPQR · E2EE · ephemeral · N-party
Covert communications for private group conversations.
Invite, talk, close the client, and the chat vanishes.
End-to-end encrypted with post-quantum cryptography,
both manual and epoch-based ratchet events add layers of
forward secrecy, ensuring messages remain private today
and unreadable to the computational power of tomorrow.
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.
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. :1.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:webOutput goes to web/dist/. Serve it from any static file host.
Preview the production build:
bun run --cwd web previewServes the contents of web/dist/ locally for smoke-testing the bundled
output.
Container build (single-file inline bundle):
bun build:web:containerProduces a single self-contained HTML file for embedding in the Docker
image or hosting from any static host without a build step. Run before
bun build:docker if you want the latest web client baked into the image.
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",
"systemMessages": 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+C |
Quit and wipe session |
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. Copy or download it and 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 |
| PROTOCOL-DIAGRAM | Animated visualization of a 3-party session and epochs |
| RECONNECT-DIAGRAM | Animated visualization of peers left / join ceremonies |
Tip
Documentation is available in the repo ./docs folder and published to the project wiki.
Run all tests:
bun testThis runs test:server, test:lib, and the test:cli stub in sequence.
The web/ workspace has no unit tests (browser app).
Run tests for a single package:
bun test:server # server WebSocket broker
bun test:lib # shared crypto session layerLint 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.