diff --git a/README.md b/README.md index 7c9bdfb..2749bf8 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,10 @@ console.log(response.choices[0].message.content); - **Automatic key loading**: Existing keys are loaded automatically from `client_keys/` directory (Node.js only) - **No manual intervention required**: The library handles key management automatically - **Optional persistence**: Keys can be saved to `client_keys/` directory for reuse across sessions (Node.js only) -- **Password protection**: Optional password encryption for private keys (recommended for production) +- **Password protection**: Optional password encryption for private keys (minimum 8 characters required) - **Secure permissions**: Private keys stored with restricted permissions (600 - owner-only access) +- **Automatic key rotation**: Keys are rotated on a configurable interval (default: 24 hours) to limit fingerprint lifetime +- **Explicit lifecycle management**: Call `dispose()` to immediately zero in-memory key material and stop the rotation timer ### Secure Memory Protection @@ -242,11 +244,38 @@ await client.loadKeys( ); ``` +### Resource Management + +Always call `dispose()` when finished to zero key material and stop the background rotation timer: + +```javascript +const client = new SecureChatCompletion({ + baseUrl: 'https://api.nomyo.ai:12434', + keyRotationInterval: 3600000, // rotate every hour +}); + +try { + const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [...] }); + console.log(response.choices[0].message.content); +} finally { + client.dispose(); +} +``` + +To disable key rotation entirely (e.g. short-lived scripts): + +```javascript +const client = new SecureChatCompletion({ + baseUrl: 'https://api.nomyo.ai:12434', + keyRotationInterval: 0, // disabled +}); +``` + ## ๐Ÿงช Platform Support ### Node.js -- **Minimum version**: Node.js 15+ (for `crypto.webcrypto`) +- **Minimum version**: Node.js 14.17+ - **Recommended**: Node.js 18 LTS or later - **Key storage**: File system (`client_keys/` directory) - **Security**: Full implementation with automatic key persistence @@ -269,10 +298,15 @@ await client.loadKeys( ```typescript new SecureChatCompletion(config?: { - baseUrl?: string; // Default: 'https://api.nomyo.ai:12434' - allowHttp?: boolean; // Default: false - apiKey?: string; // Default: undefined - secureMemory?: boolean; // Default: true + baseUrl?: string; // Default: 'https://api.nomyo.ai:12434' + allowHttp?: boolean; // Default: false + apiKey?: string; // Default: undefined + secureMemory?: boolean; // Default: true + timeout?: number; // Request timeout ms. Default: 60000 + debug?: boolean; // Enable verbose logging. Default: false + keyRotationInterval?: number; // Key rotation ms. 0 = disabled. Default: 86400000 (24h) + keyRotationDir?: string; // Directory for rotated keys. Default: 'client_keys' + keyRotationPassword?: string; // Password for rotated key files }) ``` @@ -280,6 +314,7 @@ new SecureChatCompletion(config?: { - `create(request: ChatCompletionRequest): Promise` - `acreate(request: ChatCompletionRequest): Promise` (alias) +- `dispose(): void` โ€” zero key material and stop rotation timer ### SecureCompletionClient @@ -289,10 +324,15 @@ Lower-level API for advanced use cases. ```typescript new SecureCompletionClient(config?: { - routerUrl?: string; // Default: 'https://api.nomyo.ai:12434' - allowHttp?: boolean; // Default: false - secureMemory?: boolean; // Default: true - keySize?: 2048 | 4096; // Default: 4096 + routerUrl?: string; // Default: 'https://api.nomyo.ai:12434' + allowHttp?: boolean; // Default: false + secureMemory?: boolean; // Default: true + keySize?: 2048 | 4096; // Default: 4096 + timeout?: number; // Request timeout ms. Default: 60000 + debug?: boolean; // Enable verbose logging. Default: false + keyRotationInterval?: number; // Key rotation ms. 0 = disabled. Default: 86400000 (24h) + keyRotationDir?: string; // Directory for rotated keys. Default: 'client_keys' + keyRotationPassword?: string; // Password for rotated key files }) ``` @@ -304,6 +344,7 @@ new SecureCompletionClient(config?: { - `encryptPayload(payload: object): Promise` - `decryptResponse(encrypted: ArrayBuffer, payloadId: string): Promise` - `sendSecureRequest(payload: object, payloadId: string, apiKey?: string): Promise` +- `dispose(): void` โ€” zero key material and stop rotation timer ## ๐Ÿ”ง Configuration diff --git a/docs/SECURITY.md b/docs/SECURITY.md index eef7e37..3029488 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -53,18 +53,28 @@ NOMYO.js implements end-to-end encryption for OpenAI-compatible chat completions - Automatic key generation on first use - File-based persistence (Node.js) - In-memory keys (browsers) - - Password protection via PBKDF2 + AES-256-CBC + - 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 (with warnings for HTTP) + - 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 + - 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** @@ -94,9 +104,10 @@ NOMYO.js implements end-to-end encryption for OpenAI-compatible chat completions โœ… **DO:** - Use HTTPS in production (enforced by default) - Enable secure memory protection (default: `secureMemory: true`) -- Use password-protected private keys in Node.js +- Use password-protected private keys in Node.js (minimum 8 characters) - Set private key file permissions to 600 (owner-only) -- Rotate keys periodically +- 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:** @@ -248,9 +259,12 @@ class SecureByteContext { try { return await fn(this.data); } finally { - // Always zero, even if exception occurs + // Always zero, even if exception occurs. + // zeroMemory failure is swallowed so it cannot mask the original error. if (this.useSecure) { - new Uint8Array(this.data).fill(0); + try { + this.secureMemory.zeroMemory(this.data); + } catch (_zeroErr) { /* intentional */ } } } } @@ -328,6 +342,11 @@ npm install nomyo-native โœ… **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