237 lines
7.7 KiB
Markdown
237 lines
7.7 KiB
Markdown
# 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"` |
|