From 30e33e4fa1a9c06b5999b30c1045cdc1bc80b978 Mon Sep 17 00:00:00 2001 From: alpha-nerd Date: Thu, 16 Apr 2026 16:46:10 +0200 Subject: [PATCH] =?UTF-8?q?docs/SECURITY.md=20gel=C3=B6scht?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/SECURITY.md | 423 ----------------------------------------------- 1 file changed, 423 deletions(-) delete mode 100644 docs/SECURITY.md diff --git a/docs/SECURITY.md b/docs/SECURITY.md deleted file mode 100644 index e229b6a..0000000 --- a/docs/SECURITY.md +++ /dev/null @@ -1,423 +0,0 @@ -# Security Documentation - NOMYO.js - -## Overview - -NOMYO.js implements end-to-end encryption for OpenAI-compatible chat completions using hybrid cryptography. This document details the security architecture, current implementation status, and limitations. - ---- - -## Encryption Architecture - -### Hybrid Encryption (AES-256-GCM + RSA-OAEP) - -**Request Encryption Flow:** -1. Client generates ephemeral AES-256 key (32 bytes) -2. Payload serialized to JSON and encrypted with AES-256-GCM -3. AES key encrypted with server's RSA-4096 public key (RSA-OAEP-SHA256) -4. Encrypted package sent to server with client's public key - -**Response Decryption Flow:** -1. Server encrypts response with new AES-256 key -2. AES key encrypted with client's RSA public key -3. Client decrypts AES key with private RSA key -4. Client decrypts response with AES key - -### Cryptographic Primitives - -| Component | Algorithm | Parameters | -|-----------|-----------|------------| -| **Symmetric Encryption** | AES-256-GCM | 256-bit key, 96-bit nonce, 128-bit tag | -| **Asymmetric Encryption** | RSA-OAEP | 4096-bit modulus, SHA-256 hash, MGF1-SHA256 | -| **Key Derivation** | PBKDF2 | 100,000 iterations, SHA-256, 16-byte salt | -| **Private Key Encryption** | AES-256-CBC | 256-bit key (from PBKDF2), 128-bit IV | - ---- - -## Current Implementation Status - -### ✅ Fully Implemented - -1. **Web Crypto API Integration** - - Platform-agnostic cryptography (Node.js 15+ and modern browsers) - - Hardware-accelerated when available - - Constant-time operations (timing attack resistant) - -2. **Hybrid Encryption** - - AES-256-GCM for payload encryption - - RSA-OAEP-SHA256 for key exchange - - Authenticated encryption (GCM provides AEAD) - - Unique nonce per encryption (96-bit random) - -3. **Key Management** - - 4096-bit RSA keys (default, configurable to 2048) - - Automatic key generation on first use - - File-based persistence (Node.js) - - In-memory keys (browsers) - - Password protection via PBKDF2 + AES-256-CBC (minimum 8-character password enforced) - - Automatic periodic key rotation (default: 24 hours, configurable, or disabled with `keyRotationInterval: 0`) - - `dispose()` method severs in-memory key references and cancels the rotation timer - -4. **Transport Security** - - HTTPS enforcement using proper URL parsing (`new URL()`) — not string prefix matching - - Certificate validation (browsers/Node.js) - - Optional HTTP for local development (explicit opt-in) - - API key validated to reject CR/LF characters (prevents HTTP header injection) - - Server error detail truncated to 100 printable characters (prevents log injection) - -5. **Memory Protection (Pure JavaScript)** - - Immediate zeroing of sensitive buffers - - Context managers for automatic cleanup (`SecureByteContext`) with guarded `finally` blocks - - Intermediate crypto buffers (password bytes, salt, IV) wrapped in `SecureByteContext` during key encryption - - HTTP request body (`ArrayBuffer`) zeroed after data is handed to the socket - - Best-effort memory management - -6. **Response Integrity** - - Decrypted response validated against required `ChatCompletionResponse` schema fields before use - - Generic error messages from all crypto operations (no internal engine details leaked) - -### ⚠️ Limitations (Pure JavaScript) - -1. **No OS-Level Memory Locking** - - Cannot use `mlock()` (Linux/macOS) or `VirtualLock()` (Windows) - - JavaScript GC controls memory lifecycle - - Memory may be paged to swap - - **Impact**: Sensitive data could be written to disk during high memory pressure - -2. **Memory Zeroing Only** - - Zeroes `ArrayBuffer` contents immediately after use - - Cannot prevent GC from copying data internally - - Cannot guarantee memory won't be swapped - - **Mitigation**: Minimizes exposure window - -3. **Browser Limitations** - - Keys not persisted (in-memory only) - - No file system access - - Subject to browser security policies - - **Impact**: Keys regenerated on each page load - ---- - -## Security Best Practices - -### Deployment - -✅ **DO:** -- Use HTTPS in production (enforced by default) -- Enable secure memory protection (default: `secureMemory: true`) -- Use password-protected private keys in Node.js (minimum 8 characters) -- Set private key file permissions to 600 (owner-only) -- Rely on automatic key rotation (`keyRotationInterval`, default 24h) to limit fingerprint lifetime -- Call `dispose()` when the client is no longer needed -- Validate server public key fingerprint on first use - -❌ **DON'T:** -- Use HTTP in production (only for localhost development) -- Disable secure memory unless absolutely necessary -- Store unencrypted private keys -- Share private keys across systems -- Store keys in public repositories - -### Key Management - -**Node.js (Recommended):** -```javascript -const client = new SecureCompletionClient({ routerUrl: 'https://...' }); - -// Generate with password protection -await client.generateKeys({ - saveToFile: true, - keyDir: 'client_keys', - password: process.env.KEY_PASSWORD // From environment variable -}); -``` - -**Browsers (In-Memory):** -```javascript -// Keys generated automatically, not persisted -const client = new SecureChatCompletion({ baseUrl: 'https://...' }); -``` - -### Environment Variables - -```bash -# .env file (never commit to git) -NOMYO_API_KEY=your-api-key -NOMYO_KEY_PASSWORD=your-key-password -NOMYO_SERVER_URL=https://api.nomyo.ai -``` - ---- - -## Cryptographic Implementation Details - -### AES-256-GCM - -```typescript -// Key generation -const key = await crypto.subtle.generateKey( - { name: 'AES-GCM', length: 256 }, - true, // extractable - ['encrypt', 'decrypt'] -); - -// Encryption -const nonce = crypto.getRandomValues(new Uint8Array(12)); // 96 bits -const ciphertext = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv: nonce, tagLength: 128 }, - key, - plaintext -); -``` - -**Security Properties:** -- **Authenticated Encryption with Associated Data (AEAD)** -- **128-bit authentication tag** prevents tampering -- **Unique nonce requirement** - Never reuse nonce with same key -- **Ephemeral keys** - New AES key per request provides forward secrecy - -### RSA-OAEP - -```typescript -// Key generation -const keyPair = await crypto.subtle.generateKey( - { - name: 'RSA-OAEP', - modulusLength: 4096, - publicExponent: new Uint8Array([1, 0, 1]), // 65537 - hash: 'SHA-256' - }, - true, - ['encrypt', 'decrypt'] -); - -// Encryption -const encrypted = await crypto.subtle.encrypt( - { name: 'RSA-OAEP' }, - publicKey, - data -); -``` - -**Security Properties:** -- **OAEP padding** prevents chosen ciphertext attacks -- **SHA-256 hash** in MGF1 mask generation -- **4096-bit keys** provide ~152-bit security level -- **No label** (standard practice for hybrid encryption) - -### Password-Protected Keys - -```typescript -// Derive key from password -const salt = crypto.getRandomValues(new Uint8Array(16)); -const passwordKey = await crypto.subtle.importKey( - 'raw', - encoder.encode(password), - 'PBKDF2', - false, - ['deriveKey'] -); - -const derivedKey = await crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt: salt, - iterations: 100000, // OWASP recommendation - hash: 'SHA-256' - }, - passwordKey, - { name: 'AES-CBC', length: 256 }, - false, - ['encrypt'] -); - -// Encrypt private key -const iv = crypto.getRandomValues(new Uint8Array(16)); -const encrypted = await crypto.subtle.encrypt( - { name: 'AES-CBC', iv: iv }, - derivedKey, - privateKeyData -); - -// Store: salt + iv + encrypted -``` - -**Security Properties:** -- **100,000 PBKDF2 iterations** (meets OWASP 2023 recommendations) -- **SHA-256 hash function** -- **16-byte random salt** (unique per key) -- **AES-256-CBC** for key encryption - ---- - -## Memory Protection Details - -### Current Implementation (Pure JavaScript) - -```typescript -class SecureByteContext { - async use(fn: (data: ArrayBuffer) => Promise): Promise { - try { - return await fn(this.data); - } finally { - // Always zero, even if exception occurs. - // zeroMemory failure is swallowed so it cannot mask the original error. - if (this.useSecure) { - try { - this.secureMemory.zeroMemory(this.data); - } catch (_zeroErr) { /* intentional */ } - } - } - } -} -``` - -**What it does:** -- ✅ Zeroes memory immediately after use -- ✅ Guarantees cleanup even on exceptions -- ✅ Minimizes exposure window - -**What it cannot do:** -- ❌ Prevent JavaScript GC from copying data -- ❌ Lock memory pages (no swap) -- ❌ Prevent core dumps from containing data -- ❌ Guarantee OS won't page data to disk - -### Future: Native Addon (Optional) - -A native Node.js addon can provide true memory protection: - -**Linux/macOS:** -```c -#include - -// Lock memory -mlock(data, length); - -// Zero and unlock -memset(data, 0, length); -munlock(data, length); -``` - -**Windows:** -```c -#include - -// Lock memory -VirtualLock(data, length); - -// Zero and unlock -SecureZeroMemory(data, length); -VirtualUnlock(data, length); -``` - -**Installation:** -```bash -# Optional dependency -npm install nomyo-native - -# Will use native addon if available, fallback to pure JS otherwise -``` - ---- - -## Threat Model - -### Protected Against - -✅ **Network Eavesdropping** -- All data encrypted end-to-end -- HTTPS transport encryption -- Authenticated encryption prevents tampering - -✅ **MITM Attacks** -- HTTPS certificate validation -- Server public key verification -- Warning on HTTP usage - -✅ **Replay Attacks** -- Unique nonce per encryption -- Authenticated encryption with GCM -- Server timestamp validation (server-side) - -✅ **Timing Attacks (Partial)** -- Web Crypto API uses constant-time operations -- No length leakage in comparisons -- Generic error messages from all crypto operations (RSA, AES) — internal engine errors not forwarded - -✅ **Concurrent Key Generation Race** -- Promise-chain mutex serialises all `ensureKeys()` callers -- No risk of multiple simultaneous key generations overwriting each other - -✅ **Key Compromise (Forward Secrecy)** -- Ephemeral AES keys -- Each request uses new AES key -- Compromise of one key affects only that request - -### Not Protected Against (Pure JS) - -⚠️ **Memory Inspection** -- Admin/root can read process memory -- Debuggers can access sensitive data -- Core dumps may contain keys -- **Mitigation**: Use native addon for `mlock` support - -⚠️ **Swap File Exposure** -- OS may page memory to disk -- Sensitive data could persist in swap -- **Mitigation**: Disable swap or use native addon - -⚠️ **Local Malware** -- Keyloggers can capture passwords -- Memory scrapers can extract keys -- **Mitigation**: Standard OS security practices - ---- - -## Comparison: JavaScript vs Python Implementation - -| Feature | Python | JavaScript (Pure) | JavaScript (+ Native Addon) | -|---------|--------|-------------------|----------------------------| -| **Encryption** | AES-256-GCM | ✅ AES-256-GCM | ✅ AES-256-GCM | -| **Key Exchange** | RSA-OAEP-4096 | ✅ RSA-OAEP-4096 | ✅ RSA-OAEP-4096 | -| **Memory Locking** | ✅ `mlock` | ❌ Not available | ✅ `mlock` | -| **Memory Zeroing** | ✅ Guaranteed | ✅ Best-effort | ✅ Guaranteed | -| **Key Persistence** | ✅ File-based | ✅ Node.js only | ✅ Node.js only | -| **Browser Support** | ❌ | ✅ | ❌ | -| **Zero Dependencies** | ❌ | ✅ | ❌ (native addon) | - ---- - -## Audit & Compliance - -### Recommendations for Production - -1. **Code Review** - - Review cryptographic implementations - - Verify key generation randomness - - Check for timing vulnerabilities - -2. **Penetration Testing** - - Test against MITM attacks - - Verify HTTPS enforcement - - Test key management security - -3. **Compliance** - - Document security architecture - - Risk assessment for pure JS vs native - - Decide if `mlock` is required for your use case - -### Known Limitations - -This implementation uses **pure JavaScript** without native addons. For maximum security: -- Consider implementing the optional native addon -- Or use the Python client in security-critical server environments -- Or accept the risk given other security controls (encrypted disk, no swap, etc.) - ---- - -## References - -- [Web Crypto API Specification](https://www.w3.org/TR/WebCryptoAPI/) -- [NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) - GCM mode -- [RFC 8017](https://datatracker.ietf.org/doc/html/rfc8017) - RSA-OAEP -- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)