docs(prd): 0070 secret-handling as a future pattern (SecretProvider, #355)
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -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
|
safe there and that backend keeps per-bottle sidecars. The invariant is a
|
||||||
hard precondition, not an aspiration.
|
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/<name>/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
|
## Design
|
||||||
|
|
||||||
### The contract (backend-agnostic)
|
### 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
|
is what lets a dedicated `gitea` runner user drop the root-equivalent
|
||||||
`docker` group — it only needs broker-socket access + `kvm`/pool group
|
`docker` group — it only needs broker-socket access + `kvm`/pool group
|
||||||
membership. This work unblocks it.
|
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,
|
- **PR #350 (netpool single-source):** the same "one source per fact,
|
||||||
composition over parallel hierarchies" discipline the contract follows.
|
composition over parallel hierarchies" discipline the contract follows.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user