3.6 KiB
@suaveplan/api-secrets-env — Environment-variable secrets adapter
Package: @suaveplan/api-secrets-env
Status: PROPOSED
Type: new-package
Stage: 4 (Base adapters) — see umbrella openspec/changes/api-plugin-foundation/
Why
The simplest secrets source: process.env (or Bun.env). 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 design doc, plugins declare their secrets in plugin.json under manifest.secrets[] (each entry has name, required: boolean, description). The installer's fail-fast contract (§4) is: any required secret missing at install or start time → refuse to install / refuse to start. api-secrets-env implements that contract by reading from env at construction and at validateAgainstManifest(manifest) time.
Stage 4 invariant: ships direct AND worker-proxy surfaces; both pass runSecretsConformance (§5.2a, §13.4).
What changes
Net-new package depending on @suaveplan/api-secrets (contract), @suaveplan/api-types, @suaveplan/api-worker-rpc, @suaveplan/error, @suaveplan/data-masking (for redaction in logs), zod.
- ADDED:
createEnvSecrets(opts)— direct-surface factory returning aSecretsLike. Reads viaBun.envfirst then falls back toprocess.env. - ADDED:
createEnvSecretsWorkerProxy(port, opts)— proxy-surface factory; the worker NEVER touches host env directly — everyget()round-trips through the proxy so secret materialisation stays in the main process. - ADDED:
EnvSecretsOptions— Zod schema (prefix?: string(default ""),caseSensitive?: boolean(default true),fallbackSource?: "process" | "bun" | "both"(default"both")). - ADDED:
validateAgainstManifest(manifest)— given a plugin manifest'ssecrets[], checks everyrequired: trueentry is present; missing → throwsSECRETS_ENV_REQUIRED_MISSINGlisting every missing name. - ADDED:
SECRETS_ENV_*error codes (SECRETS_ENV_REQUIRED_MISSING,SECRETS_ENV_INVALID_OPTIONS). - ADDED: Log redaction integration — secret VALUES never appear in error messages or telemetry; only NAMES.
Out of scope
- Watched/reactive secrets — env vars are read once at construction; rotation requires plugin reload (delegated to host hot reload §8).
- Encrypted secrets at rest —
api-secrets-vaultStage 8. - Per-tenant secrets — env-based source is host-global by definition.
Source mapping
Genesis-original. Source of design intent: docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md.
| Requirement | Design doc section |
|---|---|
| Manifest secrets installer fail-fast | §3 (manifest schema), §4 (lifecycle FSM) |
| Worker-proxy variant required | §5.2a |
| Conformance both surfaces | §13.4 |
| Value redaction | §12.2 |
Verification approach
src/factory/create-env-secrets.test.ts—get(name)returns env value; missing → undefined; redaction never leaks values.src/factory/create-env-secrets.spec-conformance.test.ts—runSecretsConformance("env-direct", ...).src/proxy/create-env-secrets-worker-proxy.test.ts— proxy unit tests; worker side has no env access.src/proxy/create-env-secrets-worker-proxy.spec-conformance.test.ts—runSecretsConformance("env-worker-proxy", ...).src/manifest-validation/validate-against-manifest.test.ts— required missing → throw with named list; optional missing → silent.src/redaction/redaction.test.ts— error messages and metrics never contain raw secret values.
Migration impact
None — additive only.