Skip to content

TheAnonymousCodingCult/ghostram

Repository files navigation

GhostRAM

GhostRAM

Ephemeral E2EE chat relay. RAM-only, zero logs, zero traces.


Ephemeral end-to-end encrypted chat relay. Everything lives in RAM — no database, no logs, no persistence. When a room is deleted or all participants leave, all data is gone forever.

E2EE by default: The relay only sees ciphertext. Encryption keys are generated in the browser (ephemeral P-256 ECDH + AES-256-GCM), exchanged peer-to-peer, and destroyed on disconnect. The relay cannot read messages.

Flow: User A → E2EE → WebSocket → GhostRAM relay (opaque ciphertext) → WebSocket → E2EE → User B

Features

  • Zero-knowledge relay — messages are end-to-end encrypted; the server only forwards ciphertext
  • RAM-only — no database, no message history, no file storage
  • Zero-log mode — disable all server output with LOGGING=0
  • Ephemeral keys — ECDH keypairs and AES group keys are discarded on disconnect
  • File transfer — encrypted file sending with chunking and image previews
  • Multi-peer — P2P rooms support up to 256 participants
  • Support mode — 2-person rooms with admin dashboard for support staff
  • Built-in admin UI — web-based dashboard to monitor and join support rooms
  • Embeddable widget — drop-in JavaScript bundle for any website
  • Cloudflare Turnstile — optional bot protection for room creation
  • Rate limiting — configurable per-IP rate limits
  • Tor compatible — run as an onion service for maximum privacy
  • Docker ready — single-command deployment

Room modes

Mode Participants Join token
p2p Up to maxParticipants (default 16, max 256) Yes — share with trusted peers only
support Exactly 2. A third join attempt destroys the room. No — use API key or open WebSocket

When the last client leaves, the room is removed from memory.

Quick start

git clone https://github.com/TheAnonymousCodingCult/ghostram.git
cd ghostram
npm install
cp .env.example .env    # edit API keys
npm start

Open http://localhost:8765/admin/ (set ANOCHAT_ADMIN_KEY first).

Docker

cp .env.example .env    # edit API keys
docker compose up --build -d

The container binds to 127.0.0.1:8765 by default. Put a reverse proxy (Caddy, nginx) in front for TLS.

Configuration

All settings are in .env (see .env.example for all options):

Variable Default Description
PORT 8765 HTTP/WS listen port
ANOCHAT_API_KEY (empty) Required for POST /rooms. Never expose in client-side code.
ANOCHAT_ADMIN_KEY (empty) Enables admin dashboard at /admin/
REQUIRE_WS_API_KEY 0 If 1, WebSocket requires API key (P2P guests with valid joinToken are always allowed)
MAX_ROOMS 1000 Max rooms in memory (0 = unlimited)
ROOM_MAX_IDLE_MS 14400000 Auto-delete idle rooms after 4h (0 = disabled)
TRUST_PROXY 0 Set 1 behind nginx/Caddy so rate limits use X-Forwarded-For
LOGGING 1 Set 0 for zero console output (zero-knowledge mode)
TURNSTILE 0 Enable Cloudflare Turnstile for POST /rooms
TURNSTILE_SECRET_KEY (empty) Server-side Turnstile secret
TURNSTILE_SITE_KEY (empty) Published at GET /public-config for the widget

HTTP API

If ANOCHAT_API_KEY is set, protected routes require: X-API-Key: <key> or Authorization: Bearer <key>.

Method Path Auth Description
GET /health No Liveness check
POST /rooms API key Create room. Body: { "mode": "p2p"|"support" }. Returns roomId, joinToken (P2P only).
GET /rooms API key List all rooms
GET /rooms/:roomId API key Room metadata
DELETE /rooms/:roomId API key or joinToken Close room, disconnect all participants

WebSocket

wss://your-relay.com/ws?roomId=<uuid>&clientId=<unique-per-tab>&joinToken=<token>
  • roomId — must exist (create via API first)
  • clientId — unique per browser tab
  • joinToken — required for P2P rooms (returned from POST /rooms)
  • apiKey — optional query param if REQUIRE_WS_API_KEY=1

All messages (text, binary) are broadcast to every other participant in the room.

End-to-end encryption

The E2EE module (shared/e2ee-session.js) runs entirely in the browser:

  1. Each client generates an ephemeral ECDH P-256 keypair on connect
  2. Clients exchange public keys via hs (handshake) frames through the relay
  3. The room creator generates a random AES-256-GCM group key
  4. The group key is encrypted per-peer using ECDH-derived wrap keys and distributed via wrap frames
  5. All chat messages and files are encrypted with the shared group key
  6. On disconnect, all keys are zeroed and discarded

The relay never sees plaintext. Verify encryption integrity by comparing Safety Numbers out-of-band.

Admin dashboard

Built into the relay — no external app required.

  1. Set ANOCHAT_ADMIN_KEY to a long random secret
  2. Open https://your-relay.com/admin/
  3. Enter the admin key (and optionally the relay API key for WebSocket access)
  4. Dashboard lists open support rooms. Join opens an E2EE chat panel. Close destroys the room.
Method Path Header Description
GET /admin/api/health X-Admin-Key Sanity check
GET /admin/api/support-rooms X-Admin-Key List support rooms
GET /admin/api/stats X-Admin-Key Detailed server stats
DELETE /admin/api/rooms/:roomId X-Admin-Key Force-close room

Embeddable widget

Build the IIFE bundle:

npm run build:widget

Output: widget/dist/anochat-widget.js. Host on your CDN or static server.

<div id="chat-root"></div>
<script src="https://your.cdn/anochat-widget.js"></script>
<script>
  AnochatWidget.init({
    apiBaseUrl: 'https://your-relay.com',
    mode: 'p2p',
    title: 'Encrypted Chat',
    container: document.getElementById('chat-root'),
    // For P2P guests joining via invite link:
    // roomId: '...', joinToken: '...', isP2PGuest: true
  });
</script>

See widget/README.md for all options.

Security: Never put ANOCHAT_API_KEY in public JavaScript. Create rooms on your backend and pass roomId + joinToken to the frontend.

Integration pattern (recommended)

┌─────────────┐     POST /rooms      ┌─────────────┐
│  Your App   │ ───────────────────► │  GhostRAM   │
│  Backend    │  (with API key)      │   relay     │
│             │ ◄─────────────────── │             │
│             │  roomId + joinToken  │             │
└──────┬──────┘                      └──────┬──────┘
       │ roomId + joinToken                 │
       ▼                                    │
┌─────────────┐     WebSocket (E2EE)        │
│  Browser    │ ◄──────────────────────────►│
│  (widget)   │  ciphertext only            │
└─────────────┘                             │

Your backend holds the API key. The browser only gets roomId + joinToken and connects directly to the relay via WebSocket. The relay forwards encrypted frames — it cannot read them.

Zero-log deployment

For maximum privacy:

# .env
LOGGING=0
# docker-compose.yml
services:
  ghostram:
    logging:
      driver: "none"

Reverse proxy (Caddy example):

ghostram.example.com {
    reverse_proxy localhost:8765
    log {
        output discard
    }
}

Tor onion service

GhostRAM can run as a Tor hidden service. Add a Tor sidecar to your Docker setup or configure torrc to forward to the relay port. See DEPLOYMENT.md for step-by-step instructions.

Users connecting via Tor Browser use the .onion address. The widget supports dual URLs (clearnet + onion) — detect the access method client-side and connect to the appropriate relay.

Architecture

  • Relay: Node.js + Express + ws. Stateless message forwarding. All room state in a Map in process memory.
  • E2EE: shared/e2ee-session.js — browser-only module. Ephemeral ECDH + AES-GCM. Key-setup frames serialized via lightweight mutex; chat messages decrypt in parallel.
  • Widget: widget/ — Vite-built IIFE bundle that imports the shared E2EE module.
  • Admin: public/admin/ — static HTML/JS dashboard importing the same shared E2EE module.

Scaling

Each relay instance has its own memory. Clients in the same room must hit the same instance (sticky sessions). For horizontal scaling, add shared state (Redis, etc.) — not included.

License

MIT

About

GhostRAM — Ephemeral E2EE group chat relay (ECDH + AES-256-GCM). RAM-only, zero-knowledge, zero-log. Docker + Caddy + Tor ready.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors