Initial Commit
This commit is contained in:
82
packages/api-stack/api/api-secrets-worker-proxy/README.md
Normal file
82
packages/api-stack/api/api-secrets-worker-proxy/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# @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.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## 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 `<redacted>` marker and `redactSecret` helper from `@suaveplan/api-secrets`, plus an additiona…
|
||||
- [redaction](./src/redaction/redaction.md) — Re-exports the canonical `<redacted>` 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…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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) — `<redacted>` 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`.
|
||||
|
||||
Reference in New Issue
Block a user