Files
skills/docs/api-stack/api/api-secrets-env
2026-05-29 08:20:56 +01:00
..
2026-05-29 08:20:56 +01:00
2026-05-29 08:20:56 +01:00

@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 SecretsLike implementation that resolves secret names against the host process's environment variabl…
    • adapter — Stage-4 concrete SecretsLike implementation that resolves secret names against the host process's environment variabl…
  • 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…
  • error-codes — Stable error-code registry and dedicated SuaveplanError subclass for the env-backed secrets adapter. Every failure mo…
    • error-codes — Stable error-code registry and dedicated SuaveplanError subclass for the env-backed secrets adapter. Every failure mo…

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:

  1. enumerating the plugin's permitted names via list() — the same names the manifest declares;
  2. returning undefined from get() for a permitted-but-absent name so enforceRequiredSecrets can detect it;
  3. throwing SECRETS_ENV.KEY_NOT_FOUND from require() 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