feat!: make RSA keys ephemeral by default
Some checks failed
NYX Security Scan / nyx-scan (pull_request) Failing after 5m51s

BREAKING CHANGE: keyDir now defaults to null (ephemeral) instead of
'client_keys'. Clients that relied on keys persisting across restarts
must now pass keyDir explicitly.

The Python SDK defaults to key_dir=None: a key pair is generated in
memory for the session and never written to disk. The JS port defaulted
to 'client_keys' and always persisted, so merely constructing a client
wrote an RSA private key into the working directory. That is a weaker
default than the client it ports, and one users never asked for.

  - keyDir?: string | null, defaulting to undefined. null and undefined
    both mean ephemeral, matching Python's None.
  - Persistent mode is unchanged when keyDir is set: load the existing
    pair from that directory, otherwise generate and save one there.
  - Browsers are always ephemeral; they have no filesystem.

Key rotation follows the same rule. It previously hardcoded
'client_keys' as its fallback directory, so an ephemeral client would
have started writing private keys to disk on the first rotation tick.
Rotated keys are now persisted only where keyDir or keyRotationDir is
explicitly configured.

Tests assert the intent (that saveKeys is never called) rather than
probing the filesystem, since a leftover client_keys/ from the old
default would otherwise make them pass or fail for the wrong reason.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Alpha Nerd 2026-07-19 11:19:03 +02:00
parent 2495f1e6e8
commit 987acf8816
Signed by: alpha-nerd
SSH key fingerprint: SHA256:QkkAgVoYi9TQ0UKPkiKSfnerZy2h4qhi3SVPXJmBN+M
7 changed files with 260 additions and 39 deletions

View file

@ -90,14 +90,27 @@ JavaScript's `delete` and variable reassignment do not zero the underlying memor
### 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.
Keys are **ephemeral by default**: a fresh pair is generated in memory on first use, lives only for the lifetime of the client, and is never written to disk. Nothing to protect at rest, nothing to rotate out of a directory, no key material surviving the process. This matches the Python SDK's `key_dir=None` default.
To reuse a key pair across runs, set `keyDir` explicitly (Node.js only):
```typescript
const client = new SecureChatCompletion({
baseUrl: 'https://api.nomyo.ai',
keyDir: '/var/lib/myapp/nomyo-keys',
});
```
The directory is created on first use and reloaded on subsequent runs:
```
client_keys/
<keyDir>/
private_key.pem # permissions 0600 (owner-only)
public_key.pem # permissions 0644
```
Persisting keys is a deliberate trade-off: it buys session continuity at the cost of a long-lived private key on disk. Prefer the ephemeral default unless you specifically need key reuse.
### Configure the Key Directory
```javascript
@ -154,7 +167,7 @@ const client2 = new SecureChatCompletion({
### 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`.
This applies only when you opt into persistence via `keyDir`; ephemeral keys never reach the filesystem. Private key files are saved with `0600` permissions (owner read/write only) on Unix-like systems. Add your `keyDir` and `*.pem` to your `.gitignore``client_keys/` and `*.pem` are already included if you use this package's default `.gitignore`.
---
@ -210,9 +223,10 @@ const client = new SecureChatCompletion({
- [ ] 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`
- [ ] Prefer the ephemeral key default; only set `keyDir` if you genuinely need key reuse across runs
- [ ] If persisting keys: use password-protected key files (`keyRotationPassword`)
- [ ] If persisting keys: store them outside the project directory and outside version control
- [ ] If persisting keys: add your `keyDir` and `*.pem` to `.gitignore`
- [ ] Call `client.dispose()` when the client is no longer needed
- [ ] Consider the native addon if swap-file exposure is unacceptable