7.7 KiB
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.
Encryption Mechanism
Hybrid Encryption
Each request uses a two-layer scheme:
- AES-256-GCM encrypts the payload (authenticated encryption — prevents tampering).
- 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:
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.
// 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
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:
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:
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:
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:
// 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:
const client = new SecureChatCompletion({
apiKey: process.env.NOMYO_API_KEY,
});
Production Checklist
- Always use HTTPS (
allowHttpisfalseby 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*.pemto.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" |