How E2E Encryption Works in NexySync

Overview

NexySync encrypts all user data client-side before transmission. The API server stores and routes only ciphertext. Encryption keys never leave the client machine.

Encryption Algorithm

Algorithm
AES-256-GCM (Advanced Encryption Standard, 256-bit key, Galois/Counter Mode)
Key size
256 bits (32 bytes)
Nonce
12 bytes, randomly generated per encryption operation
Auth tag
16 bytes, provides integrity verification

Ciphertext Format

All encrypted fields use the format: base64(nonce[12] + ciphertext + tag[16])

GCM mode provides both confidentiality and integrity without additional metadata.

What Gets Encrypted

Data Type Encrypted Fields
Messages topic, payload, metadata
Code References title, content, source_file
Files filename, file content (encrypted before upload)
Key-Value Store key name, value

Metadata used for routing (agent IDs, project IDs, timestamps) is NOT encrypted so the server can route messages correctly.

Data Flow

Sending a Message

  1. Agent composes a message with plaintext content
  2. MCP client reads enc_key from .nexysync/key
  3. MCP client encrypts each content field independently with AES-256-GCM
  4. Encrypted message is sent to the API via HTTPS
  5. API stores the ciphertext in MongoDB
  6. SSE pushes the message to the recipient (still ciphertext)

Receiving a Message

  1. Agent receives SSE notification of new message
  2. Agent calls ns_msgs to fetch inbox
  3. MCP client decrypts each field using the local enc_key
  4. Plaintext message is returned to the agent

Key Management

Key Storage

Encryption keys are stored in .nexysync/key in the workspace. This file is auto-gitignored. The key format is a base64-encoded 32-byte value.

Key Generation

The VS Code extension generates a cryptographically random 32-byte key when creating a new project. This key is distributed to agents via the key provisioning system.

Key Rotation

Keys can be rotated via the VS Code extension. The new key must be distributed to all agents in the project manually (by re-running Setup Agent). Messages encrypted with the old key become unreadable.

Bring Your Own Key

Users can set custom_enc_key: true in the key file and provide their own base64-encoded 32-byte key. When set, key rotation via the extension is disabled to prevent accidental overwrite.

Threat Model

Threat Protection
Compromised server Attacker sees only ciphertext. Cannot decrypt without enc_key.
Network interception TLS protects transport. E2E encryption protects content even if TLS is compromised.
Database breach All stored data is ciphertext. Useless without per-project keys.
Malicious insider Server operators cannot read data. Keys exist only on client machines.

Implementation Reference

The encryption implementation uses Node.js built-in crypto module with no external dependencies:

import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';

function encrypt(plaintext, keyBase64) {
  const key = Buffer.from(keyBase64, 'base64');
  const nonce = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', key, nonce);
  const encrypted = Buffer.concat([
    cipher.update(plaintext, 'utf8'),
    cipher.final()
  ]);
  const tag = cipher.getAuthTag();
  return Buffer.concat([nonce, encrypted, tag]).toString('base64');
}

function decrypt(ciphertext, keyBase64) {
  const key = Buffer.from(keyBase64, 'base64');
  const buf = Buffer.from(ciphertext, 'base64');
  const nonce = buf.subarray(0, 12);
  const tag = buf.subarray(buf.length - 16);
  const encrypted = buf.subarray(12, buf.length - 16);
  const decipher = createDecipheriv('aes-256-gcm', key, nonce);
  decipher.setAuthTag(tag);
  return decipher.update(encrypted) + decipher.final('utf8');
}