137 lines
5.8 KiB
Markdown
137 lines
5.8 KiB
Markdown
# @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.
|
|
|
|
<!-- begin:auto-docs -->
|
|
## Documentation tree
|
|
|
|
- [adapter](./src/adapter/adapter.md) — The adapter module builds a concrete `SecretsLike` over the `Bun.secrets` API — the cross-platform wrapper Bun ships ar…
|
|
- [adapter](./src/adapter/adapter.md) — The adapter module builds a concrete `SecretsLike` over the `Bun.secrets` API — the cross-platform wrapper Bun ships ar…
|
|
- [allowlist](./src/allowlist/allowlist.md) — The allow-list module encapsulates the per-plugin scoping rule that defines the package's security boundary: a plugin c…
|
|
- [allowlist](./src/allowlist/allowlist.md) — The allow-list module encapsulates the per-plugin scoping rule that defines the package's security boundary: a plugin c…
|
|
- [descriptor-builder](./src/descriptor-builder/descriptor-builder.md) — This module produces the `SecretDescriptor[]` shape the plugin manifest emits in its `secrets[]` block from a compact a…
|
|
- [descriptor-builder](./src/descriptor-builder/descriptor-builder.md) — This module produces the `SecretDescriptor[]` shape the plugin manifest emits in its `secrets[]` block from a compact a…
|
|
- [error-codes](./src/error-codes/error-codes.md) — This module owns the stable `SECRETS_BUN.*` code registry and the `SecretsBunError` subclass thrown by every failure pa…
|
|
- [error-codes](./src/error-codes/error-codes.md) — This module owns the stable `SECRETS_BUN.*` code registry and the `SecretsBunError` subclass thrown by every failure pa…
|
|
- [otel](./src/otel/instruments.md) — This module owns the OpenTelemetry instrument bundle the `Bun.secrets`-backed adapter emits and a lazy cache that re-bi…
|
|
- [instruments](./src/otel/instruments.md) — This module owns the OpenTelemetry instrument bundle the `Bun.secrets`-backed adapter emits and a lazy cache that re-bi…
|
|
<!-- end:auto-docs -->
|
|
|
|
## Install
|
|
|
|
```bash
|
|
bun add @suaveplan/api-secrets-bun
|
|
```
|
|
|
|
## Usage
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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
|
|
|