crypthold
v2.2.1
Published
Secure encrypted config store for Node.js (HKDF, key rotation, lockfile concurrency, atomic writes).
Maintainers
Readme
Crypthold
Secure, hardened, atomic configuration store for Node.js applications.
Crypthold is a production-grade configuration substrate designed for high-integrity environments. It provides tamper-evident storage, cross-process safety, and seamless key management.
Features
- Hardened Security: AES-256-GCM encryption with HKDF-SHA256 key derivation.
- Integrity First: SHA-256 content hashing prevents silent external tampering.
- Concurrency Safe: Robust cross-process locking (
.lock) with automatic stale recovery. - Reactive: Live configuration watching with debounced callbacks.
- Key Lifecycle: Native rotation support and multi-key fallback decryption.
- Production Ready: Atomic writes,
fsyncsupport, and comprehensive diagnostic reports. - OS Keychain: Native integration via
@napi-rs/keyring.
Installation
npm install cryptholdQuick Start
import { Crypthold } from 'crypthold'
const store = new Crypthold({ appName: 'my-app' })
// Initialize (once) or Load
await store.load()
// Set and Persist
await store.set('db_password', 'secret123')
// Get with Types
const pass = store.get<string>('db_password')Advanced Usage
Reactive Configuration
Watch for external changes (e.g., manual file edits or other processes):
store.watch(
(data) => {
console.log('Config updated:', data)
},
{ debounceMs: 50 },
)
deterministicSeedis intended for tests only and now throws outsideNODE_ENV=test.
Key Rotation
Transition to a new master key without data loss:
await store.rotate('new-64-char-hex-key')Diagnostics (Doctor)
Check the health and integrity of your configuration store:
const report = await store.doctor()
// { keyPresent: true, integrityOk: true, permissionsOk: true, lockExists: false ... }Durability
Ensures data is physically written to disk (useful for high-stakes environments):
const store = new Crypthold({
appName: 'my-app',
durability: 'fsync',
})Ensures data is physically written to disk (useful for high-stakes environments):
const store = new Crypthold({
appName: 'my-app',
encryptionKeyEnvVar: 'CRYPTHOLD_KEY',
})
await store.load()Examples
The examples/ directory contains detailed demonstration scripts for advanced scenarios:
- Key Lifecycle: Demonstrates multi-key fallback decryption and atomic key rotation.
- DX & Safety: Shows usage of the
doctor()diagnostic report,watch()for live updates, and encrypted import/export. - Durability & Tests: Covers
fsyncdurability modes, optimistic concurrency conflict detection, and deterministic test mode.
Run any example with:
npm run build
node --experimental-strip-types examples/filename.tsAPI Reference
new Crypthold(options)
| Option | Type | Description |
| :----------------------- | :----------------------- | :------------------------------------------------------- |
| appName | string | Required. Service name for Keychain and AAD context. |
| configPath | string | Custom path for the encrypted store file. |
| encryptionKeyEnvVar | string | Env var name for the primary master key. |
| encryptionKeySetEnvVar | string | Env var for multi-key sets (id1:hex,id2:hex). |
| maxFileSizeBytes | number | Limit to prevent memory blow (default: 10MB). |
| durability | "normal" \| "fsync" | Atomic write strategy (default: "normal"). |
| lockTimeoutMs | number | Max wait time for lock acquisition (default: 5s). |
| conflictDetection | "strict" \| "metadata" | Conflict check mode (strict default). |
Methods
| Method | Description |
| :---------------------- | :------------------------------------------------------------ |
| init() | Generates a new master key in the keychain. |
| load() | Loads and decrypts the store. Supports legacy migration. |
| get<T>(key) | Retrieves a value from memory cache. |
| set(key, value) | Updates memory cache and persists atomically. |
| rotate(newKey?) | Re-encrypts the entire store with a new key. |
| watch(callback) | Starts watching for file changes. Returns unwatch function. |
| doctor() | Performs diagnostic checks on keys and file integrity. |
| exportEncrypted(path) | Safely clones the encrypted store. |
| importEncrypted(path) | Loads an external store into the local substrate. |
Error Codes
| Code | Description |
| :------------------------- | :------------------------------------------------------------ |
| CRYPTHOLD_INTEGRITY_FAIL | Decryption or AAD verification failed (Tampering detected). |
| CRYPTHOLD_CONFLICT | File changed externally during a write (Hash/mtime mismatch). |
| CRYPTHOLD_LOCK_TIMEOUT | Failed to acquire process lock within timeout. |
| CRYPTHOLD_FILE_TOO_LARGE | Store exceeds maxFileSizeBytes limit. |
| CRYPTHOLD_KEY_NOT_FOUND | Master key is missing from environment/keychain. |
| CRYPTHOLD_UNSAFE_OPTION | A test-only option was used in a non-test environment. |
Security
- Encryption: AES-256-GCM with 96-bit random IV (NIST SP 800-38D).
- Key Derivation: HKDF-SHA256 ensures key separation for every write.
- Binding: AAD (Additional Authenticated Data) binds ciphertext to your
appName. - Permissions: Enforces
0600(Owner Read/Write) on Unix-like systems.
License
Apache-2.0
