⚗️ Client-side open

Verify the encryption yourself

Nenyok publishes its client-side encryption code for public review. The server relay code remains private to reduce attack surface — but it has no ability to decrypt your messages. Here we explain exactly what is open, what the limits are, and how to audit it.

What is and isn't open
✓ Open

Client encryption (chat.js)

Full AES-256-GCM logic, PBKDF2 key derivation, random IV per message. Runs in your browser.

✓ Open

Interface (index.html, chat.html)

The UI code — what you see is what runs. Inspectable in DevTools at any time.

✗ Private

Server relay code

Forwards encrypted blobs only — cannot decrypt. Kept private to reduce infrastructure attack surface.

✗ Private

Server configuration & IPs

Infrastructure details not disclosed to prevent targeted attacks.

⚠️ Important limitation

The server delivers the JavaScript to your browser on each visit. If the server were compromised, it could serve modified JS. This is a fundamental limitation of any browser-based app. To mitigate: check the source in DevTools before sensitive conversations, or use the browser's built-in crypto verification (see below).

Threat model — what we protect against

Nenyok is designed for a specific threat model. It is not a universal security solution.

✓ Protected

Passive network eavesdropping

ISP, network admin, or anyone intercepting traffic cannot read messages — only encrypted ciphertext is transmitted.

✓ Protected

Server-side access

The relay server sees only encrypted blobs. It has no key and cannot decrypt messages.

✓ Protected

Message logging

Nothing is stored on disk. Messages exist only in RAM while both users are connected, then discarded.

✗ Not protected

Compromised device

If your device has malware, keyloggers, or a malicious browser extension — all bets are off.

✗ Not protected

Weak shared code

A short or guessable code (under 12 chars, dictionary words) can be brute-forced. Use a random passphrase.

✗ Not protected

Compromised server delivering modified JS

A server-side attacker could serve different code. Always verify JS in DevTools for sensitive use.

~ Partial

MITM on initial connection

HTTPS prevents MITM on the connection itself. Key fingerprint (shown in chat) lets you verify the session manually.

~ Partial

Metadata

The server knows when connections happen and connection IP. It does not know who you are or what you say.

Encryption code

The core cryptographic functions from chat.js. This is what actually runs in your browser.

chat.js — key derivation & encryption JavaScript · Web Crypto API
// ── Key derivation ──────────────────────────────────────────
// PBKDF2-SHA256 · 200,000 iterations · 16-byte random salt
// The salt is random per session — different each time
// Both peers derive the same key independently

async function deriveKey(code, salt) {
  const enc = new TextEncoder();
  const baseKey = await crypto.subtle.importKey(
    'raw', enc.encode(code), 'PBKDF2', false, ['deriveKey']
  );
  return crypto.subtle.deriveKey(
    { name: 'PBKDF2', hash: 'SHA-256', iterations: 200000, salt: salt.buffer },
    baseKey,
    { name: 'AES-GCM', length: 256 },
    true,              // exportable for fingerprint computation
    ['encrypt', 'decrypt']
  );
}

// ── Key fingerprint ──────────────────────────────────────────
// Short visual hash of the derived key
// Both users see the same fingerprint if keys match
// Verify it verbally to detect MITM

async function computeFingerprint(key) {
  const raw = await crypto.subtle.exportKey('raw', key);
  const hash = await crypto.subtle.digest('SHA-256', raw);
  const arr = Array.from(new Uint8Array(hash));
  return arr.slice(0, 6).map(b => b.toString(16).toUpperCase().padStart(2,'0')).join(':');
}

// ── Encryption ───────────────────────────────────────────────
// Random 12-byte IV per message — never reused
// AES-256-GCM provides both confidentiality and integrity

async function encryptMessage(key, text) {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    new TextEncoder().encode(text)
  );
  return { iv: Array.from(iv), data: Array.from(new Uint8Array(encrypted)) };
}

// ── Decryption ───────────────────────────────────────────────
// Returns null if decryption fails (wrong key or tampered data)
// GCM tag validates integrity — no silent corruption

async function decryptMessage(key, payload) {
  try {
    const decrypted = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv: new Uint8Array(payload.iv) },
      key,
      new Uint8Array(payload.data)
    );
    return new TextDecoder().decode(decrypted);
  } catch {
    return null; // wrong key or integrity check failed
  }
}
How to audit the code yourself

You don't need to trust us — verify directly in your browser.

1

Open DevTools → Sources → chat.js

In Chrome/Firefox: F12 → Sources tab → nenyok.com → chat.js. Compare the deriveKey, encryptMessage, decryptMessage functions with what is shown on this page.

2

Check Network tab — no plaintext leaves the browser

Open DevTools → Network → WS. You will see only JSON blobs with iv and data arrays — never your message text.

3

Verify key fingerprint with your contact

When connected, both users see a key fingerprint in the status bar (e.g. A1:B2:C3:D4:E5:F6). Call or text your contact and confirm the fingerprints match. If they don't — someone is in the middle.

4

Confirm Web Crypto API is used

In DevTools Console, type window.crypto.subtle — it should return a SubtleCrypto object. All Nenyok crypto goes through this browser-native API, not a third-party library.

🔒 Security Policy

Scope: client-side encryption, session handling, key derivation, WebSocket protocol.

Disclosure: Please report privately — do not post publicly until resolved.

Contact: info@nenyok.com

Response SLA: acknowledgement within 48 hours, resolution timeline communicated within 5 business days.

We credit researchers who report valid vulnerabilities.

Data & logging
Not stored

Message content

Messages are never written to disk. They exist in server RAM only while both users are in the same room.

Temporary

Connection metadata

IP addresses and connection timestamps appear in server process logs. Logs are not persisted long-term and are not shared with third parties.

Active

Rate limiting

20 messages/second per connection, 20 room joins/minute per IP. Protects against abuse.

Enforced

2 users per room

The server rejects any third connection to an occupied room. No silent observers possible.