Documentation
¶
Overview ¶
Package vaultresolve is read-time vault-credential resolution: it turns a session's attached vault_ids into the environment-variable bindings a sandbox is provisioned with. Resolution reads current rows every time it runs (no cache), so rotation and archive propagate without a session restart (docs/plan/12_vaults-credentials.md, D5).
Two resolutions share one selection rule (winnersFor, first-vault-wins), so they can never disagree on which credential a secret_name resolves to. Bindings yields the sandbox-visible half: each active environment_variable credential's secret_name paired with an opaque placeholder derived per (session, secret_name) (internal/egress) — stable across re-provision. Credentials yields the gate half: the same winners with their secrets decrypted, which the per-session egress gate substitutes back for the placeholder at egress time. The sandbox only ever sees the placeholder; the secret is never injected into it.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrCredentialUnusable = errors.New("the matched credential cannot be used")
ErrCredentialUnusable marks a credential that matched and cannot be used: its secret purged, its cipher absent, its sealed bytes unreadable, its token missing or unsendable. The operator has something to fix and retrying will not fix it.
An error without it says nothing about the credential — a failed query, a cipher backend that timed out — and is worth a retry rather than an answer. The two travel out of one function and the caller's response to them differs, so they cannot be told apart by position.
Functions ¶
func MCPCredentialFor ¶ added in v0.3.0
func MCPCredentialFor(ctx context.Context, db DB, cipher secrets.Cipher, vaultIDs []string, serverURL string) (string, error)
MCPCredentialFor resolves the bearer token a session's attached vaults register for an MCP server at serverURL — static_bearer's `token` or mcp_oauth's `access_token` — or "" when none of them register one, in which case the caller dials with whatever the URL itself carries and nothing more. That is what the reference documents ("When no MCP credential matches by mcp_server_url, the connection is attempted unauthenticated and will error if the server requires authentication").
The token is plaintext and memory-only, like Credential.Secret — never logged, never stored, and never quoted into an error.
Matching is by normalized URL (see normalizeMCPURL), and "the first vault with a match wins" — the same rule, in the same vaultIDs order, that winnersFor applies to environment_variable credentials by secret_name. The two cannot share a query: that one is hard-filtered to environment_variable, and these rows are keyed by a URL that has to be normalized in Go before it can be compared at all.
Read fresh on every call, no cache, so a rotation or an archive reaches a running session's next dial — the property the reference calls re-resolution.
A vault whose winning credential cannot be used — its secret purged, its cipher gone — does not fall through to a later vault. The first vault matched, and matching is what the rule is about; falling through would authenticate with a credential the reference would not have chosen. The dial then goes out unauthenticated and the server's refusal is the operator's signal. An mcp_oauth credential whose expires_at has passed is refreshed before it is handed back, and the rotation is stored — see [refreshIfDue].
Types ¶
type Binding ¶
Binding is one resolved environment-variable credential: the sandbox-visible env var, injected at provision as SecretName=Placeholder. The placeholder is opaque and inert on its own — a request that carries it egresses the literal token until the gate substitutes the real secret.
func Bindings ¶
func Bindings(ctx context.Context, q Querier, sessionID string, vaultIDs []string) ([]Binding, error)
Bindings resolves the active environment_variable credentials of sessionID's attached vaults into placeholder bindings. When several attached vaults carry the same secret_name, the first vault in vaultIDs order wins (D5). An archived vault contributes nothing: archiving a vault archives and purges its credentials, so the archived_at filter already excludes them. Placeholders are derived per (session, secret_name), so resolution is fully deterministic — a re-provision or the egress gate recovers the exact tokens already injected.
type Credential ¶
type Credential struct {
CredentialID string // vcrd_… — non-secret; the substitution span's credential_id (plan 12)
VaultID string // vlt_… — the containing vault; a credential_host_unreachable_error names both ids
SecretName string // the sandbox env-var name; the winner key
Placeholder string // egress.Placeholder(sessionID, SecretName) — identical to the Binding's token
Secret string // plaintext secret_value; memory-only
AllowedHosts []string // the limited allow-list (nil when Unrestricted)
Unrestricted bool // the credential's networking arm
Header bool // injection_location.header
Body bool // injection_location.body
}
Credential is one resolved environment_variable credential ready for the egress gate: the sandbox-visible Placeholder, the Secret it stands for, the credential's own networking half (Unrestricted, or AllowedHosts for limited), and the injection locations it is enabled for. It is the gate-side twin of a Binding — both resolve from the same winner (see winnersFor), so a placeholder injected into the sandbox and the secret substituted at egress always agree.
Secret is plaintext: it lives only in the resolving process's memory and the config response body — never logged, never stored (docs/plan/12, D1).
func Credentials ¶
func Credentials(ctx context.Context, q Querier, cipher secrets.Cipher, sessionID string, vaultIDs []string) ([]Credential, error)
Credentials resolves the active environment_variable credentials of sessionID's attached vaults into their decrypted secrets — the gate's substitution set. It shares winnersFor with Bindings, so both halves select the same credential per secret_name (first vault in vaultIDs order wins).
A cipher is required once the session has any active environment_variable credential (a winner) — even one whose ciphertext was purged: a vault-attached session on a cipher-less deployment is a misconfiguration, not a silent no-op. A decrypt failure or a sealed document missing its secret_value fails the whole call — a cipher outage or tampering deserves a loud, fail-closed error rather than a partial set that would let a placeholder egress as a literal with no signal. An active credential whose ciphertext is absent (a purge anomaly, not reachable through the write path) is skipped: its sandbox placeholder simply egresses literally, which is fail-closed. Error messages carry credential ids, never secret or ciphertext bytes.
type DB ¶ added in v0.3.0
type DB interface {
Querier
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
}
DB is Querier plus the one write resolution performs: storing an mcp_oauth credential's rotated tokens after a dial-time refresh (see MCPCredentialFor). The two are separate because the environment-variable resolutions genuinely only read, and widening their parameter would say otherwise.