nomyo-js/doc/security-guide.md

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:

  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:

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
});

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 (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"