# `@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 a `SecretsLike`. Reads via `Bun.env` first then falls back to `process.env`. - ADDED: `createEnvSecretsWorkerProxy(port, opts)` — proxy-surface factory; the worker NEVER touches host env directly — every `get()` 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's `secrets[]`, checks every `required: true` entry is present; missing → throws `SECRETS_ENV_REQUIRED_MISSING` listing 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-vault` Stage 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.