fix: base_url
doc: created
This commit is contained in:
parent
6e02559f4e
commit
43165f86f2
17 changed files with 2151 additions and 293 deletions
237
doc/security-guide.md
Normal file
237
doc/security-guide.md
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
# 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"` |
|
||||
Loading…
Add table
Add a link
Reference in a new issue