Files
suaveplan/packages/api-stack/api/api-secrets-bun
2026-05-27 23:29:53 +01:00
..
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00

@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 SecretsLike over the Bun.secrets API — the cross-platform wrapper Bun ships ar…
    • adapter — The adapter module builds a concrete SecretsLike over the Bun.secrets API — the cross-platform wrapper Bun ships ar…
  • 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 its secrets[] block from a compact a…
    • descriptor-builder — This module produces the SecretDescriptor[] shape the plugin manifest emits in its secrets[] block from a compact a…
  • error-codes — This module owns the stable SECRETS_BUN.* code registry and the SecretsBunError subclass thrown by every failure pa…
    • error-codes — This module owns the stable SECRETS_BUN.* code registry and the SecretsBunError subclass thrown by every failure pa…
  • 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…

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 returned null. get() resolves to undefined; 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.secrets is missing (running under Node without the shim), the OS keystore daemon is not running, or the adapter has been dispose()d.
  • SECRETS_BUN.WRITE_FAILED — set/delete failed at the OS layer. Always wraps the underlying error as cause.
  • 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