# @suaveplan/api-secrets-worker-proxy — worker-side proxy to a main-process SecretsLike adapter. Marshals every ctx.secrets.get/require/list call across api-worker-rpc, enforces a per-plugin allowed-keys whitelist on the main side BEFORE the secret value crosses the wire, and provides no worker-side caching so a rotated secret is picked up immediately and a compromised worker cannot exfiltrate cached values. Stage-3 worker → main `SecretsLike` proxy for the API plugin system. ## Documentation tree - [error-codes](./src/error-codes/error-codes.md) — Frozen registry of error codes thrown across the worker → main `SecretsLike` proxy boundary. The codes here are distinc… - [error-codes](./src/error-codes/error-codes.md) — Frozen registry of error codes thrown across the worker → main `SecretsLike` proxy boundary. The codes here are distinc… - [main-side](./src/main-side/main-side.md) — Mounts a concrete `SecretsLike` adapter onto a main-process `MessagePort` so a worker-isolated API plugin can request s… - [main-side](./src/main-side/main-side.md) — Mounts a concrete `SecretsLike` adapter onto a main-process `MessagePort` so a worker-isolated API plugin can request s… - [redaction](./src/redaction/redaction.md) — Re-exports the canonical `` marker and `redactSecret` helper from `@suaveplan/api-secrets`, plus an additiona… - [redaction](./src/redaction/redaction.md) — Re-exports the canonical `` marker and `redactSecret` helper from `@suaveplan/api-secrets`, plus an additiona… - [testing](./src/testing/testing.md) — Real, no-mock testing harness for the worker-side secrets proxy. Wraps a real `MessageChannel` (via `api-worker-rpc`'s… - [testing](./src/testing/testing.md) — Real, no-mock testing harness for the worker-side secrets proxy. Wraps a real `MessageChannel` (via `api-worker-rpc`'s… - [worker-side](./src/worker-side/worker-side.md) — Worker-side `SecretsLike` proxy. Every method call is marshalled across the `api-worker-rpc` transport to the main-side… - [worker-side](./src/worker-side/worker-side.md) — Worker-side `SecretsLike` proxy. Every method call is marshalled across the `api-worker-rpc` transport to the main-side… ## What it does - **Main side** vends a concrete `SecretsLike` over a `MessagePort` via `api-worker-rpc`, enforcing a per-plugin allowed-keys whitelist BEFORE the secret value crosses the wire. - **Worker side** exposes a `SecretsLike` proxy. Every method call round-trips to the main side; **there is no worker-side cache** of values. ## Security invariants 1. A non-manifested key is rejected on the main side with `SECRETS_PROXY.KEY_NOT_PERMITTED`. The underlying source is never consulted, so a malicious worker cannot probe the source's state. 2. `list()` returns the FROZEN snapshot of `allowedKeys`, not the source's `list()`. The worker cannot enumerate names outside its manifest. 3. Two sequential `get("X")` calls produce TWO main-side invocations. No caching means a rotated secret is picked up immediately AND a compromised worker has no internal state to exfiltrate. 4. `close()` makes every subsequent method reject with `SECRETS_PROXY.PROXY_DISPOSED`. ## Modules - [`error-codes`](./src/error-codes/error-codes.md) — `SECRETS_PROXY.*` registry + `SecretsProxyError` class. - [`main-side`](./src/main-side/main-side.md) — `createSecretsProxyServer(port, secrets, allowedKeys)`. - [`worker-side`](./src/worker-side/worker-side.md) — `createSecretsProxyClient(port, opts?)`. - [`redaction`](./src/redaction/redaction.md) — `` marker + `redactSecretLine`. - [`testing`](./src/testing/testing.md) — real `MessageChannel` + `InMemorySecrets` harness. ## Quick start ```ts import { createSecretsProxyServer } from "@suaveplan/api-secrets-worker-proxy/main-side"; import { createSecretsProxyClient } from "@suaveplan/api-secrets-worker-proxy/worker-side"; import { createInProcessPortPair } from "@suaveplan/api-worker-rpc"; const ports = createInProcessPortPair(); const server = createSecretsProxyServer(ports.serverPort, mySecrets, ["STRIPE_KEY"]); const proxy = createSecretsProxyClient(ports.clientPort, { initialList: ["STRIPE_KEY"] }); const key = await proxy.require("STRIPE_KEY"); proxy.close(); server.close(); ports.close(); ``` ## Conformance `tests/conformance/run-secrets-conformance.test.ts` runs `runSecretsConformance` from `@suaveplan/api-secrets/testing` against the worker-side proxy. This is The precondition gate — every scenario that passes against a direct in-process adapter MUST also pass through the proxy surface before Stage 4 can complete. ## Dependencies - `@suaveplan/api-secrets` — canonical contract + redaction marker. - `@suaveplan/api-types` — `SecretsLike` type. - `@suaveplan/api-worker-rpc` — `MessagePort`-based RPC transport. - `@suaveplan/error`, `tslib`, `zod`.