OpenAI compatible secure chat client with end-to-end encryption on NOMYO Inference Endpoints https://e2ee.nomyo.ai/
Find a file
alpha nerd 00388ccb01
All checks were successful
NYX Security Scan / nyx-scan (pull_request) Successful in 5m49s
feat: lock sensitive buffers in memory; drop new Function from jose loading
Memory protection (step 3 of the Python parity work):

NodeSecureMemory has implemented lockMemory/unlockMemory since the
native addon landed, but nothing ever called them — mlock was dead code
while getProtectionInfo() reported method: 'mlock' and canLock: true.
The library claimed a protection it was not applying.

  - SecureByteContext now locks on entry and unlocks on exit, mirroring
    Python's secure_bytearray(lock=True). Zeroing happens *before*
    unlocking, so cleartext cannot reach swap in between.
  - lockMemory/unlockMemory are part of the SecureMemory interface, so
    the browser implementation must answer for them explicitly (false).
  - Locking is best-effort throughout: a refused or throwing lock
    degrades to zeroing only, which still has value.

getProtectionInfo() now probes rather than assumes. Having the addon
loaded is not the same as being allowed to lock: mlock is routinely
refused by RLIMIT_MEMLOCK, which is small by default and 0 in some
containers. canLock reflects a real mlock attempt, and 'mlock' is only
claimed when locking genuinely works — otherwise it reports zero-only
and says why. ProtectionInfo also carries platform, hasSecureZeroing
and pageSize, closer to Python's get_protection_info().

SAST (ts.code_exec.new_function, ERROR):

JwtQuoteVerifier loaded ESM-only jose via new Function('s', 'return
import(s)'). No user input reached it, so it was not code injection —
but new Function is blocked by any CSP without 'unsafe-eval', and this
package ships a browser bundle, so the failure would land in the
attestation path. Removed in favour of injection: pass the module as
options.jose when require('jose') cannot work. The error message says
so. No eval-equivalent remains in src/.

.nyx/triage.json records the two native/src/mlock.cc cfg-resource-leak
warnings as false positives, scoped to that file rather than the rule,
so a genuine leak in future C++ still surfaces.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 11:34:18 +02:00
.forgejo/workflows .forgejo/workflows/opencode.yml aktualisiert 2026-06-26 14:28:51 +02:00
.nyx feat: lock sensitive buffers in memory; drop new Function from jose loading 2026-07-19 11:34:18 +02:00
doc feat!: make RSA keys ephemeral by default 2026-07-19 11:19:03 +02:00
examples fix: base_url 2026-04-16 16:44:26 +02:00
native Update dependency node-gyp to v13 2026-06-12 13:28:30 +00:00
src feat: lock sensitive buffers in memory; drop new Function from jose loading 2026-07-19 11:34:18 +02:00
tests feat: lock sensitive buffers in memory; drop new Function from jose loading 2026-07-19 11:34:18 +02:00
.gitignore fix: imports and SecureByteContext in node 2026-04-16 15:40:41 +02:00
CONTRIBUTING.md fix: base_url 2026-04-16 16:44:26 +02:00
jest.config.js fix: restore build and tests under TypeScript 6 2026-07-19 10:59:28 +02:00
LICENSE LICENSE aktualisiert 2026-04-16 16:47:13 +02:00
package-lock.json fix: repair npm install and the rollup build 2026-07-19 11:10:36 +02:00
package.json fix: repair npm install and the rollup build 2026-07-19 11:10:36 +02:00
README.md feat!: make RSA keys ephemeral by default 2026-07-19 11:19:03 +02:00
renovate.json Add renovate.json 2026-05-10 09:24:55 +00:00
rollup.config.mjs fix: repair npm install and the rollup build 2026-07-19 11:10:36 +02:00
tsconfig.json fix: restore build and tests under TypeScript 6 2026-07-19 10:59:28 +02:00

NOMYO.js — Secure JavaScript Chat Client

OpenAI-compatible secure chat client with end-to-end encryption for NOMYO Inference Endpoints

  • All prompts and responses are automatically encrypted and decrypted
  • Hybrid encryption: AES-256-GCM payload + RSA-OAEP-SHA256 key exchange, 4096-bit keys
  • Drop-in replacement for OpenAI's ChatCompletion API
  • Works in both Node.js and browsers

Quick Start

Installation

npm install nomyo-js

Basic Usage (Node.js)

import { SecureChatCompletion } from 'nomyo-js';

const client = new SecureChatCompletion({
  apiKey: process.env.NOMYO_API_KEY,
});

const response = await client.create({
  model: 'Qwen/Qwen3-0.6B',
  messages: [{ role: 'user', content: 'Hello!' }],
  temperature: 0.7,
});

console.log(response.choices[0].message.content);
client.dispose();

Basic Usage (Browser)

<script type="module">
  import { SecureChatCompletion } from 'https://unpkg.com/nomyo-js/dist/browser/index.js';

  const client = new SecureChatCompletion({
    baseUrl: 'https://api.nomyo.ai',
    apiKey:  'your-api-key',
  });

  const response = await client.create({
    model: 'Qwen/Qwen3-0.6B',
    messages: [{ role: 'user', content: 'What is 2+2?' }],
  });

  console.log(response.choices[0].message.content);
</script>

Documentation

Full documentation is in the doc/ directory:

  • Getting Started — walkthrough for new users
  • API Reference — complete constructor options, methods, types, and error classes
  • Models — available models and selection guide
  • Security Guide — encryption, memory protection, key management, compliance
  • Rate Limits — limits, automatic retry behaviour, batch throttling
  • Examples — 12+ code examples for common scenarios
  • Troubleshooting — error reference and debugging tips

Security Features

Hybrid Encryption

  • Payload encryption: AES-256-GCM (authenticated encryption)
  • Key exchange: RSA-OAEP-SHA256
  • Key size: 4096-bit RSA keys
  • Scope: All communication is end-to-end encrypted

Key Management

  • Ephemeral by default: A fresh key pair is generated in memory on first use and never written to disk. Set keyDir to persist and reuse keys across runs (Node.js only); existing keys in that directory are reloaded automatically.
  • Password protection: Optional AES-encrypted private key files (minimum 8 characters).
  • Secure permissions: Private key files saved at 0600 (owner-only).
  • Auto-rotation: Keys rotate every 24 hours by default (configurable via keyRotationInterval).
  • Explicit lifecycle: Call dispose() to zero in-memory key material and stop the rotation timer.

Secure Memory

The library wraps all intermediate sensitive buffers (AES keys, plaintext payload, decrypted bytes) in SecureByteContext, which zeroes them in a finally block immediately after use.

Pure JavaScript cannot lock pages to prevent OS swapping (mlock). For environments where swap-file exposure is unacceptable, install the optional nomyo-native addon. Check the current protection level:

import { getMemoryProtectionInfo } from 'nomyo-js';

const info = getMemoryProtectionInfo();
// Without addon: { method: 'zero-only', canLock: false }
// With addon:    { method: 'mlock',     canLock: true  }

Security Tiers

Pass security_tier per request to route inference to increasingly isolated hardware:

Tier Hardware Use case
"standard" GPU General secure inference
"high" CPU/GPU balanced Sensitive business data
"maximum" CPU only HIPAA PHI, classified data
const response = await client.create({
  model:         'Qwen/Qwen3-0.6B',
  messages:      [{ role: 'user', content: 'Patient record summary...' }],
  security_tier: 'maximum',
});

Usage Examples

With API Key

const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY });

Error Handling

import {
  SecureChatCompletion,
  AuthenticationError,
  RateLimitError,
  ForbiddenError,
} from 'nomyo-js';

try {
  const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [...] });
} catch (err) {
  if (err instanceof AuthenticationError) console.error('Check API key:', err.message);
  else if (err instanceof RateLimitError)    console.error('Rate limit hit:', err.message);
  else if (err instanceof ForbiddenError)    console.error('Model/tier mismatch:', err.message);
  else throw err;
}

Per-Request Router Override

Send a single request to a different router without changing the main client:

const response = await client.create({
  model:    'Qwen/Qwen3-0.6B',
  messages: [{ role: 'user', content: 'Hello from secondary router' }],
  base_url: 'https://secondary.nomyo.ai:12435',  // temporary — main client unchanged
});

Tool / Function Calling

const response = await client.create({
  model:    'Qwen/Qwen3-0.6B',
  messages: [{ role: 'user', content: "What's the weather in Paris?" }],
  tools: [
    {
      type: 'function',
      function: {
        name:        'get_weather',
        description: 'Get weather information for a location',
        parameters: {
          type: 'object',
          properties: { location: { type: 'string' } },
          required: ['location'],
        },
      },
    },
  ],
  tool_choice: 'auto',
});

Thinking Models

const response = await client.create({
  model:    'LiquidAI/LFM2.5-1.2B-Thinking',
  messages: [{ role: 'user', content: 'Is 9.9 larger than 9.11?' }],
});

const { content, reasoning_content } = response.choices[0].message;
console.log('Reasoning:', reasoning_content);
console.log('Answer:',    content);

Resource Management

const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY });

try {
  const response = await client.create({ model: 'Qwen/Qwen3-0.6B', messages: [...] });
  console.log(response.choices[0].message.content);
} finally {
  client.dispose();  // zeros key material, stops rotation timer
}

Local Development (HTTP)

const client = new SecureChatCompletion({
  baseUrl:   'http://localhost:12435',
  allowHttp: true,  // required — also prints a visible warning
});

API Reference

SecureChatCompletion — Constructor Options

new SecureChatCompletion(config?: ChatCompletionConfig)
Option Type Default Description
baseUrl string 'https://api.nomyo.ai' NOMYO router URL. Must be HTTPS in production.
allowHttp boolean false Allow HTTP connections. Local development only.
apiKey string undefined Bearer token for Authorization header.
secureMemory boolean true Zero sensitive buffers immediately after use.
timeout number 60000 Request timeout in milliseconds.
debug boolean false Print verbose logging to the console.
keyDir string | null null Directory to load/save RSA keys on startup. Omit for ephemeral in-memory keys that are never written to disk. Node.js only.
keyRotationInterval number 86400000 Auto-rotate keys every N ms. 0 disables rotation.
keyRotationDir string keyDir Directory for rotated key files. Rotated keys are only persisted when keyDir or keyRotationDir is set. Node.js only.
keyRotationPassword string undefined Password for encrypted rotated key files.
maxRetries number 2 Extra retry attempts on 429/5xx/network errors. Exponential backoff (1 s, 2 s, …).

Methods

  • create(request): Promise<ChatCompletionResponse> — send an encrypted chat completion
  • acreate(request): Promise<ChatCompletionResponse> — alias for create()
  • dispose(): void — zero key material and stop the rotation timer

create() Request Fields

All standard OpenAI fields (model, messages, temperature, top_p, max_tokens, stop, n, tools, tool_choice, user, frequency_penalty, presence_penalty, logit_bias) plus:

Field Description
security_tier "standard" | "high" | "maximum" — hardware isolation level
api_key Per-request API key override
base_url Per-request router URL override — creates a temporary client, used once, then disposed

SecureCompletionClient — Constructor Options

Lower-level client. All options above apply, with these differences:

Option Type Default Description
routerUrl string 'https://api.nomyo.ai' Base URL (baseUrl is renamed here)
keySize 2048 | 4096 4096 RSA modulus length

Methods

  • generateKeys(options?) — generate a new RSA key pair
  • loadKeys(privateKeyPath, publicKeyPath?, password?) — load existing PEM files
  • fetchServerPublicKey() — fetch the server's RSA public key
  • encryptPayload(payload) — encrypt a request payload
  • decryptResponse(encrypted, payloadId) — decrypt a response body
  • sendSecureRequest(payload, payloadId, apiKey?, securityTier?) — full encrypt → POST → decrypt cycle
  • dispose() — zero key material and stop rotation timer

Secure Memory Public API

import {
  getMemoryProtectionInfo,
  disableSecureMemory,
  enableSecureMemory,
  SecureByteContext,
} from 'nomyo-js';
Export Description
getMemoryProtectionInfo() Returns { method, canLock, isPlatformSecure, details? }
disableSecureMemory() Disable global secure-memory zeroing
enableSecureMemory() Re-enable global secure-memory zeroing
SecureByteContext Low-level buffer wrapper — zeros in finally block

Error Classes

import {
  AuthenticationError, InvalidRequestError, RateLimitError,
  ForbiddenError, ServerError, ServiceUnavailableError,
  APIConnectionError, SecurityError, DisposedError, APIError,
} from 'nomyo-js';
Class HTTP Thrown when
AuthenticationError 401 Invalid or missing API key
InvalidRequestError 400 Malformed request
ForbiddenError 403 Model not allowed for the security tier
RateLimitError 429 Rate limit exceeded (after all retries)
ServerError 500 Internal server error (after all retries)
ServiceUnavailableError 503 Backend unavailable (after all retries)
APIError varies Other HTTP errors
APIConnectionError Network failure or timeout (after all retries)
SecurityError HTTPS not used, header injection, or crypto failure
DisposedError Method called after dispose()

Platform Support

Node.js

  • Minimum: Node.js 22.22.2+
  • Recommended: Node.js 22 LTS or later
  • Key storage: File system (keyDir directory, default client_keys/)

Browsers

  • Supported: Chrome 37+, Firefox 34+, Safari 11+, Edge 79+
  • Key storage: In-memory only (not persisted)
  • Limitation: File-based key operations (keyDir, loadKeys) are not available

Security Best Practices

  • Always use HTTPS (allowHttp is false by default)
  • Load API key from an environment variable, never hardcode it
  • Use password-protected key files (keyRotationPassword)
  • Store keys outside the project directory and outside version control
  • Add client_keys/ and *.pem to .gitignore
  • Call dispose() when the client is no longer needed
  • Use security_tier: 'maximum' for HIPAA PHI or classified data
  • Consider the nomyo-native addon if swap-file exposure is unacceptable

License

See LICENSE file.