@suaveplan/api-secrets-bun — SecretsLike adapter backed by Bun.secrets (macOS Keychain / Linux libsecret / Windows Credential Manager). Per-plugin allowlist enforcement, locked-keystore detection, idempotent dispose, OTel api.secrets.reads_total{backend="bun"} counter.
Stage-8 SecretsLike adapter backed by Bun.secrets (macOS Keychain,
Linux libsecret, Windows Credential Manager) with per-plugin allow-list
enforcement, OS-keystore failure-mode classification, and an idempotent
lifecycle.
The right default for local-dev and single-host operator setups: secrets never touch disk in plaintext and rotate via the OS UI.
Per-plugin allow-list keeps each plugin scoped to its manifest-declared
secrets — a leaked plugin cannot enumerate or read any keystore entry
beyond what its manifest.secrets[] declared. Values never appear in
logs or errors — only names.
Documentation tree
- adapter — The adapter module builds a concrete
SecretsLikeover theBun.secretsAPI — the cross-platform wrapper Bun ships ar…- adapter — The adapter module builds a concrete
SecretsLikeover theBun.secretsAPI — the cross-platform wrapper Bun ships ar…
- adapter — The adapter module builds a concrete
- allowlist — The allow-list module encapsulates the per-plugin scoping rule that defines the package's security boundary: a plugin c…
- allowlist — The allow-list module encapsulates the per-plugin scoping rule that defines the package's security boundary: a plugin c…
- descriptor-builder — This module produces the
SecretDescriptor[]shape the plugin manifest emits in itssecrets[]block from a compact a…- descriptor-builder — This module produces the
SecretDescriptor[]shape the plugin manifest emits in itssecrets[]block from a compact a…
- descriptor-builder — This module produces the
- error-codes — This module owns the stable
SECRETS_BUN.*code registry and theSecretsBunErrorsubclass thrown by every failure pa…- error-codes — This module owns the stable
SECRETS_BUN.*code registry and theSecretsBunErrorsubclass thrown by every failure pa…
- error-codes — This module owns the stable
- otel — This module owns the OpenTelemetry instrument bundle the
Bun.secrets-backed adapter emits and a lazy cache that re-bi…- instruments — This module owns the OpenTelemetry instrument bundle the
Bun.secrets-backed adapter emits and a lazy cache that re-bi…
- instruments — This module owns the OpenTelemetry instrument bundle the
Install
bun add @suaveplan/api-secrets-bun
Usage
import { createBunSecretsAdapter } from "@suaveplan/api-secrets-bun";
const adapter = createBunSecretsAdapter({
service: "billing-plugin",
allowlist: ["STRIPE_SECRET_KEY", "JWT_SIGNING_KEY"],
});
const key = await adapter.secrets.require("STRIPE_SECRET_KEY");
Manifest-driven allow-list
The companion buildBunSecretDescriptors builder lets a plugin derive
its manifest.secrets[] and the adapter's allowlist from a single
declaration so they cannot drift:
import {
buildBunSecretDescriptors,
descriptorAllowlist,
createBunSecretsAdapter,
} from "@suaveplan/api-secrets-bun";
const descriptors = buildBunSecretDescriptors([
"STRIPE_SECRET_KEY",
{ name: "OPTIONAL_WEBHOOK_KEY", required: false },
]);
const adapter = createBunSecretsAdapter({
service: "billing-plugin",
allowlist: descriptorAllowlist(descriptors),
});
// `descriptors` goes on the plugin manifest's `secrets[]`.
Capability
This package declares secrets.bun. Plugins that specifically require
the Bun keystore (e.g. desktop runtimes that bundle Bun) can pin the
backend in requiredCapabilities; otherwise the host's host.secrets.backend
config selects this adapter alongside the abstract secrets capability.
Failure modes
Distinguishes:
SECRETS_BUN.NOT_FOUND— permitted name; OS keystore returnednull.get()resolves toundefined;require()throws.SECRETS_BUN.NOT_ALLOWED— name not in the plugin's allow-list. Raised BEFORE any keystore round-trip.SECRETS_BUN.KEYSTORE_LOCKED— OS keystore is locked. The adapter does NOT fall back silently — operator must unlock.SECRETS_BUN.KEYSTORE_UNAVAILABLE—Bun.secretsis missing (running under Node without the shim), the OS keystore daemon is not running, or the adapter has beendispose()d.SECRETS_BUN.WRITE_FAILED—set/deletefailed at the OS layer. Always wraps the underlying error ascause.SECRETS_BUN.INVALID_OPTIONS— adapter construction failed Zod validation.
OpenTelemetry
Emits:
api.secrets.reads_total{backend="bun"}— successful reads.api.secrets.not_found_total{backend="bun"}— permitted-but-absent.api.secrets.denied_total{backend="bun"}— allow-list rejections.
Metric names are backend-agnostic so dashboards roll up across every
api.secrets.* source via the backend label.
Lifecycle
adapter.dispose() is idempotent — repeat calls are no-ops. Post-dispose
reads / writes throw KEYSTORE_UNAVAILABLE rather than leaking the
keystore reference.
Conformance
Passes runSecretsConformance from @suaveplan/api-secrets/testing
unchanged — see src/adapter/adapter.spec-conformance.test.ts.
Modules
| Module | Subpath | Purpose |
|---|---|---|
adapter |
./adapter |
createBunSecretsAdapter factory + types. |
allowlist |
./allowlist |
Per-plugin allow-list with assertAllowed. |
descriptor-builder |
./descriptor-builder |
Compact → SecretDescriptor[]. |
error-codes |
./error-codes |
SECRETS_BUN.* registry + SecretsBunError. |
otel |
./otel |
OTel instruments + reset hook. |
License
MIT