Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.3 KiB
@suaveplan/api-secrets-env — SecretsLike adapter backed by process.env / Bun.env with per-plugin allowed-keys enforcement. Reads from an injectable source function so tests never mutate the real environment; an optional prefix scopes lookups for multi-tenant single-host deployments.
Stage-4 SecretsLike adapter backed by process.env / Bun.env with
per-plugin allowed-keys enforcement.
The simplest secrets source: read from the host's environment. Suitable for local development, single-host deployments, container orchestrators that inject secrets as env vars, and anywhere the operator already has tooling around env-var injection.
Per-plugin allow-list keeps each plugin scoped to its manifest-declared
secrets — a worker or untrusted plugin cannot enumerate or read any env
var beyond what its manifest.secrets[] declared. Values never appear
in logs or errors — only names.
Documentation tree
- adapter — Stage-4 concrete
SecretsLikeimplementation that resolves secret names against the host process's environment variabl…- adapter — Stage-4 concrete
SecretsLikeimplementation that resolves secret names against the host process's environment variabl…
- adapter — Stage-4 concrete
- descriptor-builder — Convenience builder that turns a compact allow-list declaration into a fully-typed
SecretDescriptor[]suitable for em…- descriptor-builder — Convenience builder that turns a compact allow-list declaration into a fully-typed
SecretDescriptor[]suitable for em…
- descriptor-builder — Convenience builder that turns a compact allow-list declaration into a fully-typed
- error-codes — Stable error-code registry and dedicated
SuaveplanErrorsubclass for the env-backed secrets adapter. Every failure mo…- error-codes — Stable error-code registry and dedicated
SuaveplanErrorsubclass for the env-backed secrets adapter. Every failure mo…
- error-codes — Stable error-code registry and dedicated
Install
bun add @suaveplan/api-secrets-env
Usage
import { createEnvSecrets } from "@suaveplan/api-secrets-env";
const secrets = createEnvSecrets({
allowedKeys: ["STRIPE_SECRET_KEY", "JWT_SIGNING_KEY"],
});
const key = await secrets.require("STRIPE_SECRET_KEY");
Prefix for multi-tenant single-host deployments
const secrets = createEnvSecrets({
allowedKeys: ["API_KEY", "WEBHOOK_SECRET"],
prefix: "BILLING_",
// Reads BILLING_API_KEY, BILLING_WEBHOOK_SECRET from env.
});
Manifest-driven allow-list
The companion buildEnvSecretDescriptors builder lets a plugin derive
its manifest.secrets[] and the adapter's allowedKeys from a single
declaration so they cannot drift:
import {
buildEnvSecretDescriptors,
descriptorAllowedKeys,
createEnvSecrets,
} from "@suaveplan/api-secrets-env";
const descriptors = buildEnvSecretDescriptors([
"STRIPE_SECRET_KEY",
{ name: "OPTIONAL_WEBHOOK_KEY", required: false },
]);
const secrets = createEnvSecrets({
allowedKeys: descriptorAllowedKeys(descriptors),
});
// `descriptors` goes on the plugin manifest's `secrets[]`.
Fail-fast contract
The plugin manifest declares each required secret with
{ name, required: true }. The host's installer reads the manifest's
secrets[] and, at install or start time, calls
enforceRequiredSecrets (from @suaveplan/api-secrets) against the
adapter's list(). Any required: true secret missing from the host's
env triggers SECRETS.SECRET_REQUIRED_MISSING_AT_START, and the
installer refuses to install / refuses to start.
This adapter contributes to that contract by:
- enumerating the plugin's permitted names via
list()— the same names the manifest declares; - returning
undefinedfromget()for a permitted-but-absent name soenforceRequiredSecretscan detect it; - throwing
SECRETS_ENV.KEY_NOT_FOUNDfromrequire()so a plugin that chooses to defer the check past install also fails loudly.
Allowed-keys enforcement
Every get / require checks the allow-list BEFORE consulting the env
source. A non-permitted name throws
SECRETS_ENV.KEY_NOT_PERMITTED and never reads from the snapshot —
the env source can be expensive or have side effects without affecting
this ordering. list() returns the frozen allowedKeys array, so the
adapter never surfaces any name outside the plugin's manifest.
Secret-value redaction
Secret VALUES never appear in error messages, log lines, or telemetry
emitted by this package — only NAMES. Errors carry the name on
error.context.name for diagnostic surfaces; the value (when one was
resolved) never leaves the call site.
API
| Export | Subpath | Purpose |
|---|---|---|
createEnvSecrets |
./adapter |
Build a SecretsLike over env. |
CreateEnvSecretsOptions |
./adapter |
Option-bag type. |
buildEnvSecretDescriptors |
./descriptor-builder |
Compact → SecretDescriptor[]. |
descriptorAllowedKeys |
./descriptor-builder |
Extract names for allowedKeys. |
SECRETS_ENV_CODES |
./error-codes |
Frozen SECRETS_ENV.* code registry. |
SecretsEnvError |
./error-codes |
Thrown by every failure mode. |
Conformance
Passes runSecretsConformance from @suaveplan/api-secrets/testing
unchanged — see src/adapter/adapter.spec-conformance.test.ts.
License
MIT