Hard-Chat – A serverless, RAM-only P2P terminal chat

Sep 07, 2026 06:59 AM - 1 week ago 7

End-to-end encrypted P2P chat, correct successful your browser. No server, nary accounts, nary stored history.

by Hardlint Cybersecurity Team


This task is distributed for acquisition and information investigation purposes. It does not guarantee network-level anonymity: it protects the content of conversations, not needfully who is connecting. Read the Attack Surface and Known Limitations conception earlier utilizing it for delicate communications.

Use of a trustworthy VPN connected some devices is powerfully recommended.


  • 🔐 End-to-end encryption — AES-GCM 256-bit, cardinal derived via PBKDF2 (100,000 iterations)
  • 🌐 True P2P connection — nonstop WebRTC nexus betwixt the 2 devices, nary cardinal server relaying messages
  • 🚫 Zero persistence — nary cookies, nary localStorage, nary database: adjacent the tab and thing remains
  • 🔑 Single shared secret — a randomly generated Room Key (100 characters), nary manual method configuration required
  • 🧹 Panic Purge — 1 fastener instantly wipes keys, relationship state, and visible chat history
  • 📡 Reliable connectivity — 18 STUN/TURN servers configured arsenic fallbacks to activity moreover down restrictive NATs (4G/5G, firm networks)

  1. Open the page (must beryllium served complete HTTPS — e.g. via GitHub Pages, not opened arsenic a section file)
  2. Host: click [1] INITIALIZE ROOM → transcript the generated Room Key
  3. Send the Room Key to your interaction through a different channel (in person, sound call, different encrypted app)
  4. Guest: click [2] CONNECT TO ROOM → paste the received Room Key
  5. Wait for the relationship (usually a fewer seconds) → the chat opens
  6. If the relationship isn't established wrong 2 minutes, the Room Key expires automatically: make a caller 1 pinch the dedicated button

  • Modern browser pinch WebRTC and Web Crypto API support (recent Chrome, Firefox, Edge, Safari)
  • Internet entree connected some devices
  • The page must beryllium served complete HTTPS (Secure Context is required for Web Crypto API and WebRTC) — it does not activity erstwhile opened arsenic a section file
  • Both parties must person the page unfastened at the aforesaid time during the relationship attempt
  • The Room Key must beryllium copied in full, precisely 100 characters, pinch nary other spaces aliases statement breaks

🏗️ Architectural Overview

Zero-Trace Terminal is simply a fixed web exertion (HTML/CSS/JS, nary proprietary backend) that allows 2 devices to found a direct peer-to-peer connection via WebRTC, exchanging end-to-end encrypted matter messages.

Main components:

Component Role Technology
User interface Retro terminal UI Plain HTML/CSS
Signaling Makes the 2 peers "find" each other PeerJS (public unreality broker)
Data transport Encrypted P2P channel WebRTC DataChannel
NAT traversal Punching done firewalls/NAT STUN + TURN (ICE)
Encryption Message contented protection AES-GCM 256-bit + PBKDF2
Hosting Code distribution GitHub Pages (static)

There is nary proprietary exertion server: the codification runs wholly successful each user's browser. The only outer infrastructure progressive is utilized to "introduce" the 2 devices to each different (signaling) and, if needed, to relay postulation erstwhile a nonstop relationship isn't imaginable (TURN).


When a personification clicks "INITIALIZE ROOM (HOST)":

  1. A random 100-character drawstring is generated (generate100CharCode()), utilizing crypto.getRandomValues() — a cryptographically unafraid random number generator (not Math.random(), which is unsuitable for cryptographic purposes).
  2. The characteristic group includes uppercase/lowercase letters, digits, and typical symbols (-_!@#$%^&*), maximizing entropy wrong 100 characters.

This drawstring (the Room Key) is the only shared concealed the 2 parties request to exchange, out-of-band (e.g. sound message, successful person, different encrypted channel).

Deriving Keys from the Room Key

Two independent values, each pinch a different purpose, are derived from the Room Key:

A. Message encryption cardinal (PBKDF2 → AES-GCM)

PBKDF2( password = Room Key, brackish = "p2p-zero-trace-salt-v1" (fixed, hardcoded), iterations = 100,000, hash = SHA-256 ) → 256-bit AES-GCM key

B. PeerJS identifier (truncated SHA-256)

SHA-256(Room Key) → first 32 hex characters, prefixed pinch "ztt-"

This ID is utilized solely truthful that Host and Guest tin "find" each different connected the PeerJS signaling broker, without exchanging thing beyond the Room Key. It plays nary cryptographic role.

Note connected the fixed salt: the PBKDF2 brackish is hardcoded and identical crossed each sessions. This is acceptable because the "password" (Room Key) already has very precocious entropy (100 random characters) — a fixed brackish only weakens information successful scenarios involving weak, reused passwords, which does not use here.

  1. The Host creates a Peer object, registering pinch the nationalist PeerJS unreality agent utilizing the ID derived from the Room Key.
  2. The Guest, aft pasting the aforesaid Room Key, computes the aforesaid ID and calls peer.connect(id).
  3. The PeerJS agent only mediates this first speech (who wants to talk to whom) — it ne'er sees aliases transmits connection content, which by that constituent travels complete a abstracted WebRTC channel.

ICE Negotiation (NAT Traversal)

Once the 2 Peers person "introduced" themselves, WebRTC starts ICE speech to find a valid web path:

  1. Host candidates — the device's section IP addresses
  2. Server-reflexive (srflx) candidates — nationalist IP discovered via STUN
  3. Relay candidates — allocated via TURN, utilized only if a nonstop relationship fails

Configured ICE servers (in privilege order):

  • Dedicated Metered.ca TURN (own credentials, not shared) — stun.relay.metered.ca / global.relay.metered.ca
  • 7 nationalist STUN fallbacks (Google ×3, Cloudflare, Twilio, Nextcloud, stunprotocol.org, freestun)
  • 10 further nationalist TURN fallback endpoints (OpenRelay, freestun, numb.viagenie, ExpressTurn) — utilized only if the dedicated TURN besides fails

The browser automatically tries each operation and selects the first 1 that establishes a moving transmission (standard ICE algorithm, handled internally by WebRTC).

Timeout and Session Expiry

  • If the relationship isn't established wrong 120 seconds, the convention is considered expired:
    • The Peer and DataConnection are destroyed (peer.destroy(), conn.close())
    • The position shows [EXPIRED] Room Key nary longer valid
    • A fastener appears to make a caller Room Key (Host) aliases participate a caller 1 (Guest)
  • This prevents a Room Key from remaining "listening" indefinitely connected the nationalist broker.

🔐 Message Cryptographic Model

Every connection is individually encrypted earlier being sent complete the DataChannel:

1. Generate a random 12-byte IV (crypto.getRandomValues) 2. ciphertext = AES-GCM-Encrypt(key, IV, plaintext) 3. payload = IV || ciphertext (concatenated, IV successful plaintext astatine the front) 4. Send payload arsenic a Uint8Array via conn.send()

On receipt:

1. Extract the first 12 bytes arsenic the IV 2. The remainder is the ciphertext (includes the 16-byte GCM authentication tag astatine the end) 3. plaintext = AES-GCM-Decrypt(key, IV, ciphertext)

Security properties guaranteed by AES-GCM:

  • Confidentiality — cipher without the cardinal tin publication the content
  • Integrity/authenticity — immoderate tampering pinch the packet successful transit causes decryption to neglect ([ERR: DECRYPTION_FAILED]), alternatively than silently producing corrupted output

What this strategy does NOT cover:

  • Forward secrecy crossed sessions — if the aforesaid Room Key were reused crossed aggregate sessions (not the normal flow, which generates a caller 1 each time), each those sessions would stock the aforesaid derived key
  • Peer personality authentication — anyone who knows the Room Key tin connect; location is nary cryptographic verification of "who" is connected the different extremity beyond possession of the shared key

💾 Data Persistence (Client-Side)

Data Persistence Notes
Room Key None Only successful a JS variable, gone connected close/reload
Derived AES-GCM key None Same, ne'er written to disk
Chat messages None Only unrecorded successful the DOM/RAM, nary localStorage/IndexedDB
Cookies None The task uses nary astatine all
Application logs Local DevTools console only Never sent anywhere, gone erstwhile the tab closes

The "PANIC: PURGE SESSION" fastener explicitly forces:

  • Closure of the PeerConnection/DataConnection
  • Zeroing of the encryption cardinal successful memory
  • Wiping of each UI fields and the displayed connection history

👁️ What External Infrastructure Can See (Metadata)

Key constituent to understand: encryption protects content, not relationship metadata.

Service What it tin see What it CANNOT see
GitHub Pages IP and timestamp of whoever loads the page Message content, Room Key
PeerJS broker (public cloud) IP of Host and Guest, erstwhile they connect, their Peer ID (a hash of the Room Key, not the Key itself) Message content
Metered.ca TURN (if utilized arsenic relay) Source/destination IP, ports, magnitude of information transferred Message contented (already travels encrypted)
Each user's ISP That a relationship is being made to github.io / metered.ca / a PeerJS server Message content

Recommended mitigation (outside the code): usage a trustworthy VPN (e.g. Mullvad, pinch an anonymously created account) connected some devices, to debar exposing existent IP addresses to these third-party services. The task displays an definitive informing to this effect connected the scatter screen.


🎯 Attack Surface and Known Limitations

Risk Description Mitigated?
Message contented interception MITM connected TURN/network traffic ✅ Yes — end-to-end AES-GCM
Message tampering successful transit Packet manipulation ✅ Yes — GCM authentication tag
Deanonymization via IP metadata IP↔identity relationship done third-party logs ⚠️ Partial — requires a VPN client-side, not solved by the codification itself
Metered.ca relationship compromise TURN credentials are successful the nationalist codification (base64-obfuscated, not encrypted) ⚠️ Minimal deterrent, not existent security
Dependency connected third-party services GitHub Pages, PeerJS broker, Metered TURN — if suspended, the app stops working ⚠️ Not mitigated (would require afloat self-hosting)
Room Key reuse Would discuss guardant secrecy crossed sessions that reuse it ✅ Not applicable successful normal travel (a caller Key each session)

  • Frontend: HTML5, CSS3 (no framework)
  • Cryptography: Browser-native Web Crypto API (crypto.subtle) — PBKDF2, AES-GCM, SHA-256
  • P2P/Signaling: PeerJS v1.5.4 (a wrapper room complete autochthonal WebRTC), loaded from a nationalist CDN (unpkg.com)
  • NAT Traversal: WebRTC ICE (STUN/TURN) — 18 endpoints configured successful total
  • Hosting: GitHub Pages (static, automatic HTTPS)
  • Browser requirements: WebRTC support, Web Crypto API, ES6+ — requires a unafraid discourse (HTTPS); does not activity from file:// aliases content://

📝 Changelog of Major Versions

  1. v1 — Native WebRTC pinch manual SDP speech (copy/paste offer/answer)
  2. v2 — Migrated to PeerJS, automatic relationship based connected the Room Key, removed manual SDP fields
  3. v3 — Added dedicated Metered.ca TURN + aggregate nationalist STUN/TURN fallbacks
  4. v4 — Detailed ICE diagnostics (candidate logging, relationship states) — fixed a bug that overwrote PeerJS's soul arena handlers
  5. v5 — Timeout extended to 120s, Room Key expiry strategy pinch manual regeneration, VPN informing connected scatter screen, TURN credential obfuscation

Hard-Chat is 100% free, open-source, and maintained by the Hardlint Cybersecurity Team. We don't tally ads and we don't waste data. If you judge successful our ngo and want to thief america money our early self-hosted infrastructure (custom STUN/TURN servers), see supporting us!

Solana (SOL) aid address:

GSsqZCtDC7rf53U6gC5cJ4weAYYT9g7twxz9t15mfRDV

This package is provided "as is", without warranties of immoderate kind. The developers are not responsible for immoderate improper aliases forbidden usage of this tool. Users are solely responsible for complying pinch applicable laws successful their jurisdiction.

This archive is provided for informational and method archiving purposes only. It does not represent ineligible proposal regarding regulatory compliance, privacy, aliases liability for use.


Hardlint Cybersecurity Team

More