nomyo-js/doc/security-guide.md

238 lines
7.7 KiB
Markdown
Raw Normal View History

2026-04-16 16:44:26 +02:00
# Security Guide
## Overview
NOMYO.js provides end-to-end encryption for all communication between your application and NOMYO inference endpoints. Your prompts and responses are encrypted before leaving your process — the inference server never processes plaintext.
For the full cryptographic architecture and threat model see [SECURITY.md](../docs/SECURITY.md).
---
## Encryption Mechanism
### Hybrid Encryption
Each request uses a two-layer scheme:
1. **AES-256-GCM** encrypts the payload (authenticated encryption — prevents tampering).
2. **RSA-OAEP-SHA256** wraps the AES key for secure key exchange.
The server holds the RSA private key; your client generates the AES key fresh for every request.
### Per-Request Ephemeral AES Keys
- A new 256-bit AES key is generated for every `create()` call using the Web Crypto API.
- The key is never reused — forward secrecy is ensured per request.
- The key is zeroed from memory immediately after encryption.
### Key Exchange
Your client's RSA public key is sent in the `X-Public-Key` request header. The server encrypts the response with it so only your client can decrypt the reply.
---
## Memory Protection
### What the Library Does
All intermediate sensitive buffers (AES key, plaintext payload, decrypted response bytes) are wrapped in `SecureByteContext`. This guarantees they are zeroed in a `finally` block immediately after use, even if an exception occurs.
The encrypted request body (`ArrayBuffer`) is also zeroed by the Node.js HTTP client after the data is handed to the socket.
### Limitations (Pure JavaScript)
JavaScript has no direct access to OS memory management. The library cannot:
- Lock pages to prevent swapping (`mlock` / `VirtualLock`)
- Prevent the garbage collector from copying data internally
- Guarantee memory won't appear in core dumps
**Impact:** On a system under memory pressure, sensitive data could briefly be written to swap. For environments where this is unacceptable (PHI, classified), install the optional native addon or run on a system with swap disabled.
### Native Addon (Optional)
The `nomyo-native` addon adds true `mlock` support. When installed, `getMemoryProtectionInfo()` reports `method: 'mlock'` and `canLock: true`:
```javascript
import { getMemoryProtectionInfo } from 'nomyo-js';
const info = getMemoryProtectionInfo();
// Without addon: { method: 'zero-only', canLock: false }
// With addon: { method: 'mlock', canLock: true }
```
---
## Minimise Response Lifetime
The library protects all intermediate crypto material in secure memory. However, the **final parsed response object** is returned to your code, and you are responsible for how long it lives.
```javascript
// GOOD — extract what you need, then drop the response immediately
const response = await client.create({
model: 'Qwen/Qwen3.5-9B',
messages: [{ role: 'user', content: 'Summarise patient record #1234' }],
security_tier: 'maximum',
});
const reply = response.choices[0].message.content;
// Let response go out of scope here — don't hold it in a variable
// longer than necessary
// BAD — holding the full response object in a long-lived scope
this.lastResponse = response; // stored for minutes / hours
```
JavaScript's `delete` and variable reassignment do not zero the underlying memory. For sensitive data (PHI, classified), process and discard as quickly as possible — do not store in class attributes, global caches, or log files.
---
## Key Management
### Default Behaviour
Keys are automatically generated on first use and saved to `client_keys/` (Node.js). On subsequent runs the saved keys are reloaded automatically.
```
client_keys/
private_key.pem # permissions 0600 (owner-only)
public_key.pem # permissions 0644
```
### Configure the Key Directory
```javascript
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
keyDir: '/etc/myapp/nomyo-keys', // custom path, outside project directory
});
```
### Password-Protected Keys (Recommended for Production)
Protect key files with a password so they cannot be used even if the file is leaked:
```javascript
import { SecureCompletionClient } from 'nomyo-js';
const client = new SecureCompletionClient({ routerUrl: 'https://api.nomyo.ai' });
await client.generateKeys({
saveToFile: true,
keyDir: 'client_keys',
password: process.env.NOMYO_KEY_PASSWORD, // minimum 8 characters
});
```
To load password-protected keys manually:
```javascript
await client.loadKeys(
'client_keys/private_key.pem',
'client_keys/public_key.pem',
process.env.NOMYO_KEY_PASSWORD
);
```
### Key Rotation
Keys rotate automatically every 24 hours by default. Configure or disable:
```javascript
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
keyRotationInterval: 3600000, // rotate every hour
keyRotationDir: '/var/lib/myapp/keys',
keyRotationPassword: process.env.KEY_PWD,
});
// Or disable entirely for short-lived processes
const client2 = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
keyRotationInterval: 0,
});
```
### File Permissions
Private key files are saved with `0600` permissions (owner read/write only) on Unix-like systems. Add `client_keys/` and `*.pem` to your `.gitignore` — both are already included if you use this package's default `.gitignore`.
---
## Security Tiers
| Tier | Hardware | Use case |
|------|----------|----------|
| `"standard"` | GPU | General secure inference |
| `"high"` | CPU/GPU balanced | Sensitive business data, enforces secure tokenizer |
| `"maximum"` | CPU only | HIPAA PHI, classified data — maximum isolation |
Higher tiers add round-trip latency but increase hardware-level isolation.
---
## HTTPS Enforcement
The client enforces HTTPS by default. HTTP connections require explicit opt-in and print a visible warning:
```javascript
// Production — HTTPS only (default)
const client = new SecureChatCompletion({ baseUrl: 'https://api.nomyo.ai' });
// Local development — HTTP allowed with explicit flag
const devClient = new SecureChatCompletion({
baseUrl: 'http://localhost:12435',
allowHttp: true, // prints: "WARNING: Using HTTP instead of HTTPS..."
});
```
Without `allowHttp: true`, connecting over HTTP throws `SecurityError`.
The server's public key is fetched over HTTPS with TLS certificate verification to prevent man-in-the-middle attacks.
---
## API Key Security
API keys are sent as `Bearer` tokens in the `Authorization` header. The client validates that the key does not contain CR or LF characters to prevent HTTP header injection.
Never hardcode API keys in source code — use environment variables:
```javascript
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
});
```
---
## Production Checklist
- [ ] Always use HTTPS (`allowHttp` is `false` by default)
- [ ] Load API key from environment variable, not hardcoded
- [ ] Enable `secureMemory: true` (default)
- [ ] Use password-protected key files (`keyRotationPassword`)
- [ ] Store keys outside the project directory and outside version control
- [ ] Add `client_keys/` and `*.pem` to `.gitignore`
- [ ] Call `client.dispose()` when the client is no longer needed
- [ ] Consider the native addon if swap-file exposure is unacceptable
---
## Compliance Considerations
### HIPAA
For Protected Health Information (PHI):
- Use `security_tier: 'maximum'` on requests containing PHI
- Enable password-protected key files
- Ensure HTTPS is enforced (the default)
- Minimise response lifetime in memory (extract, use, discard)
### Data Classification
| Classification | Recommended tier |
|---------------|-----------------|
| Public / internal | `"standard"` |
| Confidential business data | `"high"` |
| PHI, PII, classified | `"maximum"` |