From 58e5275e8f709e770793da7c90b2f4301c3a42b8 Mon Sep 17 00:00:00 2001 From: didericis Date: Mon, 13 Jul 2026 12:10:45 -0400 Subject: [PATCH] docs(prd): 0070 secret-handling as a future pattern (SecretProvider, #355) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "Secret handling — FUTURE pattern (not v1)" subsection: vault as a separate trust domain holding long-lived roots, deriving short-lived scoped creds where the upstream allows (with the honest limit that a compromised proxy can still abuse currently-authorized access). The mechanism is a generic, user-extensible SecretProvider that generalizes PRD 0048's DeployKeyProvisioner and drops into the manifest wherever a raw token is accepted — discovered from ~/.bot-bottle/contrib like user AgentProviders. Marked explicitly as not required for the initial orchestrator; tracked as #355. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck --- docs/prds/0070-per-host-orchestrator.md | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/prds/0070-per-host-orchestrator.md b/docs/prds/0070-per-host-orchestrator.md index e10fdf1..701a33a 100644 --- a/docs/prds/0070-per-host-orchestrator.md +++ b/docs/prds/0070-per-host-orchestrator.md @@ -117,6 +117,48 @@ If a backend can't honor the invariant, source-IP consolidation is not safe there and that backend keeps per-bottle sidecars. The invariant is a hard precondition, not an aspiration. +### Secret handling — a FUTURE pattern (not v1) + +> **Status: future / not required for the initial orchestrator.** The +> initial cut can inject secrets as today; this section records the +> direction so v1 doesn't paint itself into a corner. Tracked as its own +> work in **#355** (generic `SecretProvider`). + +The residual weakness after all of the above is **long-lived credential +concentration** — the data-plane proxies must hold every bottle's upstream +tokens because the agent must never see them. You can't policy-gate the +component whose job is to *use* all the secrets, so a full RCE of a proxy +drains its authorized set regardless. Two moves bound this without +pretending to prevent it: + +1. **Vault as a separate trust domain.** The long-lived *roots* live in a + distinct process (ideally its own VM) that the byte-parsing data plane + never shares memory with. The proxies request secrets from it; the + crown jewels are not in the process an agent-facing parser can pop. +2. **Derive short-lived, scoped creds where the upstream allows it.** The + vault holds the root and mints expiring, narrowly-scoped credentials + per bottle-start (or per request) — GitHub App installation tokens, + OAuth/STS token exchange, forge deploy tokens. A compromise then leaks + short-lived material, not permanent keys. For upstreams stuck on static + keys the vault passes the value through (only the at-rest / audit / + revocation benefits apply, not lifetime reduction) — this residue is + accepted and documented, not solved. + +Even a plain per-request fetch (no derivation) still buys **at-rest** +reduction (process memory holds only in-flight secrets), a **detection / +rate-limit / revocation chokepoint**, and clean **cross-component +scoping** (an egress RCE can't request git-gate's creds). It does *not* +prevent abuse of currently-authorized access during the compromise window. + +**Mechanism (see #355):** generalize the existing `DeployKeyProvisioner` +(PRD 0048) into a user-extensible **`SecretProvider`** droppable into the +manifest anywhere a raw token value is accepted, discovered from +`~/.bot-bottle/contrib//secret_provider.py` exactly as user +`AgentProvider`s are — because maintaining every forge/cloud/OAuth +provider in-tree is untenable. The orchestrator's vault mints via these +providers; but the abstraction is shippable independently and today's +per-bottle sidecars can use it too. + ## Design ### The contract (backend-agnostic) @@ -247,6 +289,11 @@ Keep the sidecar **service one shared thing** throughout. is what lets a dedicated `gitea` runner user drop the root-equivalent `docker` group — it only needs broker-socket access + `kvm`/pool group membership. This work unblocks it. +- **Generic `SecretProvider` (#355):** the future secret-handling + mechanism (see "Secret handling") — generalizes PRD 0048's + `DeployKeyProvisioner` into a user-extensible provider that mints + short-lived creds. Shippable independently; the orchestrator's vault + mints through it. - **PR #350 (netpool single-source):** the same "one source per fact, composition over parallel hierarchies" discipline the contract follows.