crypthold
v2.2.1
Published
Secure encrypted config store for Node.js (HKDF, key rotation, lockfile concurrency, atomic writes).
Downloads
15
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
