Files
skills/plugins/document-navigator/docs/api-stack/api/api-secrets-worker-proxy
admin 9bf56a806b Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 01:10:00 +01:00
..

@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 — Frozen registry of error codes thrown across the worker → main SecretsLike proxy boundary. The codes here are distinc…
    • error-codes — Frozen registry of error codes thrown across the worker → main SecretsLike proxy boundary. The codes here are distinc…
  • main-side — Mounts a concrete SecretsLike adapter onto a main-process MessagePort so a worker-isolated API plugin can request s…
    • main-side — Mounts a concrete SecretsLike adapter onto a main-process MessagePort so a worker-isolated API plugin can request s…
  • redaction — Re-exports the canonical <redacted> marker and redactSecret helper from @suaveplan/api-secrets, plus an additiona…
    • redaction — Re-exports the canonical <redacted> marker and redactSecret helper from @suaveplan/api-secrets, plus an additiona…
  • testing — Real, no-mock testing harness for the worker-side secrets proxy. Wraps a real MessageChannel (via api-worker-rpc's…
    • testing — Real, no-mock testing harness for the worker-side secrets proxy. Wraps a real MessageChannel (via api-worker-rpc's…
  • worker-side — Worker-side SecretsLike proxy. Every method call is marshalled across the api-worker-rpc transport to the main-side…
    • worker-side — 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 — SECRETS_PROXY.* registry + SecretsProxyError class.
  • main-side — createSecretsProxyServer(port, secrets, allowedKeys).
  • worker-side — createSecretsProxyClient(port, opts?).
  • redaction — <redacted> marker + redactSecretLine.
  • testing — real MessageChannel + InMemorySecrets harness.

Quick start

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.