All checks were successful
NYX Security Scan / nyx-scan (pull_request) Successful in 5m43s
Completes the parity work (step 4). Request timeout now defaults to 900 s, matching Python, instead of 60 s. Encrypted inference cannot stream, so an entire completion arrives in one response; a long generation on a busy backend legitimately takes minutes and was timing out here while succeeding in the Python client. Error types now distinguish malformed data from integrity failures. Python raises ValueError for a bad package, a non-200 or unparseable /pki/public_key, and plaintext that will not parse, reserving SecurityError for crypto failures. This port wrapped nearly all of it in SecurityError — so a server sending malformed JSON was reported as an authentication failure, pointing debugging in exactly the wrong direction. Malformed data is now a plain Error (the JS equivalent of ValueError), carried past the deliberately opaque catch-all by a symbol marker rather than a new exported class. Genuine crypto failures still report a single vague message so they cannot serve as a decryption oracle. Also adds the missing guard Python has: decrypting without a private key now says so, instead of failing later and being reported as an integrity failure. doc/attestation.md ports the Python attestation guide to the JS API, and documents the two deliberate divergences: no verify_ssl escape hatch, and jose injection instead of a runtime dynamic import. Version 0.1.0 -> 0.3.0 to match the Python client's feature level, now that the two are at parity. Not ported: Python's warning when secure_memory=True but the SecureMemory module is unavailable. There is no JS equivalent — zeroing is always available, and the weaker case (mlock unavailable) is already reported honestly by getProtectionInfo(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
331 lines
10 KiB
Markdown
331 lines
10 KiB
Markdown
# Troubleshooting
|
|
|
|
## Authentication Errors
|
|
|
|
### `AuthenticationError: Invalid or missing API key`
|
|
|
|
The server rejected your API key.
|
|
|
|
**Causes and fixes:**
|
|
|
|
- Key not set — pass `apiKey` to the constructor or use `process.env.NOMYO_API_KEY`.
|
|
- Key has leading/trailing whitespace — check the value with `console.log(JSON.stringify(process.env.NOMYO_API_KEY))`.
|
|
- Key contains CR or LF characters — the client rejects keys with `\r` or `\n` and throws `SecurityError` before the request is sent. Regenerate the key.
|
|
|
|
```javascript
|
|
const client = new SecureChatCompletion({
|
|
apiKey: process.env.NOMYO_API_KEY, // never hardcode
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Connection Errors
|
|
|
|
### `APIConnectionError: Network error` / `connect ECONNREFUSED`
|
|
|
|
The client could not reach the router.
|
|
|
|
**Check:**
|
|
|
|
1. `baseUrl` is correct — the default is `https://api.nomyo.ai` (port **12435**).
|
|
2. You have network access to the host.
|
|
3. TLS is not being blocked by a proxy or firewall.
|
|
|
|
### `SecurityError: HTTPS is required`
|
|
|
|
You passed an `http://` URL without setting `allowHttp: true`.
|
|
|
|
```javascript
|
|
// Local dev only
|
|
const client = new SecureChatCompletion({
|
|
baseUrl: 'http://localhost:12435',
|
|
allowHttp: true,
|
|
});
|
|
```
|
|
|
|
Never set `allowHttp: true` in production — the server public key fetch and all request data would travel unencrypted.
|
|
|
|
### `APIConnectionError: Request timed out`
|
|
|
|
The default timeout is 900 seconds (15 minutes), matching the Python SDK. Encrypted inference cannot stream, so the entire completion arrives in a single response. Lower it if you want to fail faster:
|
|
|
|
```javascript
|
|
const client = new SecureChatCompletion({
|
|
apiKey: process.env.NOMYO_API_KEY,
|
|
timeout: 120000, // 2 minutes
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Key Loading Failures
|
|
|
|
### `Error: Failed to load keys: no such file or directory`
|
|
|
|
The `keyDir` directory or the PEM files inside it don't exist. This only applies when you opt into persistence — by default keys are ephemeral and no directory is touched at all. On first run with a `keyDir` set, the library generates and saves a new key pair there automatically, so make sure the directory is writable:
|
|
|
|
```javascript
|
|
const client = new SecureChatCompletion({
|
|
apiKey: process.env.NOMYO_API_KEY,
|
|
keyDir: '/var/lib/myapp/nomyo-keys', // directory must exist and be writable
|
|
});
|
|
```
|
|
|
|
### `Error: Invalid passphrase` / `Error: Failed to decrypt private key`
|
|
|
|
The password you passed to `loadKeys()` or `keyRotationPassword` doesn't match what was used to encrypt the file.
|
|
|
|
```javascript
|
|
await client.loadKeys(
|
|
'client_keys/private_key.pem',
|
|
'client_keys/public_key.pem',
|
|
process.env.NOMYO_KEY_PASSWORD, // must match the password used on generateKeys()
|
|
);
|
|
```
|
|
|
|
### `Error: RSA key too small`
|
|
|
|
The library enforces a minimum key size of 2048 bits. If you have old 1024-bit keys, regenerate them:
|
|
|
|
```javascript
|
|
await client.generateKeys({
|
|
saveToFile: true,
|
|
keyDir: 'client_keys',
|
|
keySize: 4096, // recommended
|
|
});
|
|
```
|
|
|
|
### `Error: Failed to load keys` (browser)
|
|
|
|
Key loading from files is a Node.js-only feature. In browsers, keys are generated in memory on first use. Do not call `loadKeys()` in a browser context.
|
|
|
|
---
|
|
|
|
## Rate Limit Errors
|
|
|
|
### `RateLimitError: Rate limit exceeded`
|
|
|
|
All automatic retries were exhausted. The default limit is 2 requests/second; burst allows 4 requests/second once per 10-second window.
|
|
|
|
**Fixes:**
|
|
|
|
- Reduce concurrency — avoid large `Promise.all` batches.
|
|
- Add client-side throttling (see [Rate Limits](rate-limits.md)).
|
|
- Increase `maxRetries` so the client backs off longer before giving up:
|
|
|
|
```javascript
|
|
const client = new SecureChatCompletion({
|
|
apiKey: process.env.NOMYO_API_KEY,
|
|
maxRetries: 5,
|
|
});
|
|
```
|
|
|
|
### `ServiceUnavailableError` with 30-minute cool-down
|
|
|
|
Burst limits were hit repeatedly and a cool-down was applied to your key. Wait 30 minutes, then review your request patterns.
|
|
|
|
---
|
|
|
|
## Model / Tier Errors
|
|
|
|
### `ForbiddenError: Model not allowed for this security tier`
|
|
|
|
The model you requested is not available at the security tier you specified. Try a lower tier or a different model:
|
|
|
|
```javascript
|
|
// If 'maximum' tier rejects the model, try 'high' or 'standard'
|
|
const response = await client.create({
|
|
model: 'Qwen/Qwen3.5-27B',
|
|
messages: [...],
|
|
security_tier: 'high', // try 'standard' if still rejected
|
|
});
|
|
```
|
|
|
|
See [Models — Security Tier Compatibility](models.md#security-tier-compatibility) for details.
|
|
|
|
---
|
|
|
|
## Crypto / Security Errors
|
|
|
|
### `SecurityError: Decryption failed`
|
|
|
|
The response could not be decrypted. This is intentionally vague to avoid leaking crypto details.
|
|
|
|
**Possible causes:**
|
|
|
|
- The server returned a malformed response (check `debug: true` output).
|
|
- A network proxy modified the response body.
|
|
- The server's public key changed mid-session — the next request will re-fetch it automatically.
|
|
|
|
Enable debug mode to log the raw response and narrow the cause:
|
|
|
|
```javascript
|
|
const client = new SecureChatCompletion({
|
|
apiKey: process.env.NOMYO_API_KEY,
|
|
debug: true,
|
|
});
|
|
```
|
|
|
|
### `Error: Unsupported protocol version` / `Error: Unsupported encryption algorithm`
|
|
|
|
The server sent a response in a protocol version or with an encryption algorithm not supported by this client version. Update the package:
|
|
|
|
```bash
|
|
npm update nomyo-js
|
|
```
|
|
|
|
---
|
|
|
|
## `DisposedError`: Method called after `dispose()`
|
|
|
|
You called a method on a client that has already been disposed.
|
|
|
|
```javascript
|
|
client.dispose();
|
|
await client.create(...); // throws DisposedError
|
|
```
|
|
|
|
Create a new client instance if you need to make more requests after disposal.
|
|
|
|
---
|
|
|
|
## Memory Protection Warnings
|
|
|
|
### `getMemoryProtectionInfo()` returns `method: 'zero-only'`
|
|
|
|
This is normal for a pure JavaScript installation. The library zeroes sensitive buffers immediately after use but cannot lock pages to prevent swapping (OS `mlock` requires a native addon).
|
|
|
|
```javascript
|
|
import { getMemoryProtectionInfo } from 'nomyo-js';
|
|
|
|
const info = getMemoryProtectionInfo();
|
|
// { method: 'zero-only', canLock: false, isPlatformSecure: false }
|
|
```
|
|
|
|
For environments where swap-file exposure is unacceptable (HIPAA PHI, classified data), install the optional `nomyo-native` addon or run on a system with swap disabled.
|
|
|
|
### `method: 'zero-only'` even with the native addon installed
|
|
|
|
`canLock` is probed, not assumed — the addon being loaded does not mean the OS
|
|
will grant locks. `mlock` is commonly refused by `RLIMIT_MEMLOCK`, which
|
|
defaults to a few megabytes and is `0` in some container runtimes. When that
|
|
happens `details` says so explicitly, and buffers are still zeroed.
|
|
|
|
```bash
|
|
ulimit -l # current memlock limit in KB; "unlimited" or a large value is what you want
|
|
```
|
|
|
|
Raise the limit (`ulimit -l`, systemd `LimitMEMLOCK=`, or Docker
|
|
`--ulimit memlock=-1`), or grant `CAP_IPC_LOCK`.
|
|
|
|
Note also that npm 11 gates package install scripts, so a fresh `npm ci` may skip
|
|
building the addon entirely until you run `npm approve-scripts`.
|
|
|
|
---
|
|
|
|
## Node.js-Specific Issues
|
|
|
|
### `ReferenceError: crypto is not defined`
|
|
|
|
In CommonJS modules on Node.js before v19, `crypto` is not a global. Import it explicitly:
|
|
|
|
```javascript
|
|
// CommonJS
|
|
const { webcrypto } = require('crypto');
|
|
global.crypto = webcrypto;
|
|
|
|
// Or switch to ES modules (recommended)
|
|
// package.json: "type": "module"
|
|
```
|
|
|
|
The library itself imports `crypto` correctly — this error only appears if your own application code tries to use `crypto` directly.
|
|
|
|
### `SyntaxError: Cannot use import statement in a module` / CommonJS vs ESM
|
|
|
|
The package ships both CommonJS (`dist/node/`) and ESM (`dist/esm/`) builds. Node.js selects the correct one automatically via `package.json` `exports`. If you see import errors, check that your `package.json` or bundler is not forcing the wrong format.
|
|
|
|
For ESM: set `"type": "module"` in your `package.json` or use `.mjs` file extensions.
|
|
For CommonJS: use `require('nomyo-js')` or `.cjs` extensions.
|
|
|
|
### TypeScript: `Cannot find module 'nomyo-js'` / missing types
|
|
|
|
Ensure your `tsconfig.json` includes `"moduleResolution": "bundler"` or `"moduleResolution": "node16"` and that `nomyo-js` is in `dependencies` (not just `devDependencies`):
|
|
|
|
```bash
|
|
npm install nomyo-js
|
|
```
|
|
|
|
---
|
|
|
|
## Browser-Specific Issues
|
|
|
|
### `Content Security Policy blocked`
|
|
|
|
If your app's CSP restricts `script-src` or `connect-src`, add the NOMYO API domain:
|
|
|
|
```
|
|
Content-Security-Policy: connect-src https://api.nomyo.ai;
|
|
```
|
|
|
|
### `TypeError: Failed to fetch` (CORS)
|
|
|
|
The NOMYO API includes CORS headers. If you see CORS errors in a browser, verify the `baseUrl` is correct (HTTPS, correct port) and that no browser extension is blocking the request.
|
|
|
|
### Keys not persisted across page reloads
|
|
|
|
This is expected behaviour — browsers do not have file system access. Keys are generated fresh on each page load. If you need persistent keys in a browser context, implement your own `loadKeys`/`generateKeys` wrapper using `localStorage` or `IndexedDB` (not recommended for high-security scenarios).
|
|
|
|
---
|
|
|
|
## Debugging Tips
|
|
|
|
### Enable verbose logging
|
|
|
|
```javascript
|
|
const client = new SecureChatCompletion({
|
|
apiKey: process.env.NOMYO_API_KEY,
|
|
debug: true,
|
|
});
|
|
```
|
|
|
|
Debug mode logs: key generation/loading, server public key fetches, request encryption details, retry attempts, and response decryption.
|
|
|
|
### Check memory protection status
|
|
|
|
```javascript
|
|
import { getMemoryProtectionInfo } from 'nomyo-js';
|
|
console.log(getMemoryProtectionInfo());
|
|
```
|
|
|
|
### Inspect response metadata
|
|
|
|
The `_metadata` field in every response carries server-side diagnostics:
|
|
|
|
```javascript
|
|
const response = await client.create({ ... });
|
|
console.log(response._metadata);
|
|
// {
|
|
// payload_id: '...',
|
|
// is_encrypted: true,
|
|
// encryption_algorithm: 'hybrid-aes256-rsa4096',
|
|
// security_tier: 'standard',
|
|
// memory_protection: { ... },
|
|
// }
|
|
```
|
|
|
|
### Test with minimum configuration
|
|
|
|
Strip all optional configuration and test with the simplest possible call to isolate the issue:
|
|
|
|
```javascript
|
|
import { SecureChatCompletion } from 'nomyo-js';
|
|
|
|
const client = new SecureChatCompletion({ apiKey: process.env.NOMYO_API_KEY });
|
|
const r = await client.create({
|
|
model: 'Qwen/Qwen3-0.6B',
|
|
messages: [{ role: 'user', content: 'ping' }],
|
|
});
|
|
console.log(r.choices[0].message.content);
|
|
client.dispose();
|
|
```
|