Keep your secrets truly secret. With encrypted names and values, zero friction, and strict security by default, secrets‑engine gives developers defense‑in‑depth without the hassle. It’s a TypeScript SDK for secure secret storage, powered by machine‑bound AES‑256‑GCM and hardened SQLite.
- Zero friction — No passphrase, no setup wizard. Works out of the box.
- Maximum privacy — Both key names and values are encrypted. No metadata leakage.
- Machine-bound — Encryption keys are derived from machine identity + random keyfile via scrypt.
- Defense in depth — Filesystem permission verification, HMAC integrity checks, per-entry unique IVs.
- Bun-native — Built on
bun:sqliteand Node crypto. Zero external runtime dependencies.
bun add @wgtechlabs/secrets-engineimport { SecretsEngine } from "@wgtechlabs/secrets-engine";
// Open or create a store (defaults to ~/.secrets-engine/)
const secrets = await SecretsEngine.open();
// Store secrets with dot-notation namespacing
await secrets.set("openai.apiKey", "sk-...");
await secrets.set("anthropic.apiKey", "sk-ant-...");
// Retrieve
const key = await secrets.get("openai.apiKey"); // "sk-..."
// Check existence (no decryption needed — HMAC lookup)
await secrets.has("openai.apiKey"); // true
// List keys with glob patterns
await secrets.keys("openai.*"); // ["openai.apiKey"]
// Delete
await secrets.delete("openai.apiKey");
// Clean up
await secrets.close();The SDK resolves the storage directory using this priority order:
| Priority | Option | Path |
|---|---|---|
| 1 (highest) | { path: "/custom/path" } |
Explicit path |
| 2 | { location: "xdg" } |
~/.config/secrets-engine/ |
| 3 (default) | (none) | ~/.secrets-engine/ |
// XDG-aware
const secrets = await SecretsEngine.open({ location: "xdg" });
// Custom path
const secrets = await SecretsEngine.open({ path: "/opt/myapp/secrets" });Open or create a secrets store. Returns a Promise<SecretsEngine>.
Retrieve a decrypted secret value. Returns string | null.
Retrieve a decrypted secret, throwing KeyNotFoundError if missing.
Store an encrypted secret.
Check if a key exists via HMAC hash lookup (no decryption).
Remove a secret. Returns true if deleted, false if not found.
List all key names, optionally filtered by glob pattern (e.g., "openai.*").
Irreversibly delete the entire store, keyfile, and directory.
Close the database connection and release resources. This method is async and must be awaited.
Returns a Promise<void> that resolves when the database is closed and integrity is finalized.
Breaking Change (v2.0.0): This method is now async. Update your code to await secrets.close().
Number of secrets currently stored.
Absolute path to the storage directory.
| Layer | Protection |
|---|---|
| Encryption | AES-256-GCM with unique IV per entry |
| Key derivation | scrypt (N=2¹⁷, r=8, p=1) from machine ID + random keyfile |
| Key name privacy | Both names and values encrypted; HMAC-SHA256 index |
| File permissions | Strict verification on open (700/600/400) |
| Integrity | HMAC-SHA256 of database contents in meta.json |
| Machine binding | Hostname + MAC + username + random keyfile |
| Error | When |
|---|---|
SecurityError |
File permissions too permissive |
IntegrityError |
Database HMAC verification fails |
KeyNotFoundError |
getOrThrow() for missing key |
DecryptionError |
Corrupted entry or wrong key |
InitializationError |
Cannot create store directory |
All errors extend SecretsEngineError with a .code property.
bun install
bun test
bun run typecheck
bun run lintMIT — WG Tech Labs