Documentation
¶
Overview ¶
AWS-backed tool execution - the real Vault dynamic-secrets path (aws/sts/<role>, a genuine sts:AssumeRole against the actual AWS account) alongside the existing biscuit/HTTP and DB paths. Unlike the DB path, a fresh credential is minted on EVERY call rather than cached per session: a real AssumeRole is cheap and AWS-TTL-enforced, so there's no cost reason to reuse one, and a real reason not to - AWS STS credentials can't be actively revoked once issued (see internal/capability/awssecrets.go), so minting fresh per call and keeping the TTL short is the only real way to bound one call's exposure window rather than a whole session's.
Circuit breakers: semantic loop detection (the same call repeated past a threshold in a short window - a hallucinating or looping agent retrying the same mutation) and per-session rate limiting (a blunt cap on how fast one session can drive tool calls at all). Both are cheap, in-memory, and session-scoped - not a replacement for the policy engine, an additional backstop against runaway agent behavior policy alone wouldn't catch.
DB-backed tool execution - the real Vault dynamic-secrets path (database/creds/<role>, a genuine CREATE ROLE against the actual database) alongside the existing biscuit/HTTP path. A DB-backed tool's "capability" is the Vault-minted database credential itself, verified by the database server directly - there's no biscuit token to mint or attenuate here, because nothing would verify it; the database is already the real, independent verifier the way mockupstream is for HTTP-backed tools.
Package handler implements the six-step flow every intercepted tool call goes through, regardless of which adapter (REST or MCP) produced it: inspect/validate -> classify -> policy evaluate -> class-specific handling (Class A pre-image capture, Class C tiering) -> mint scoped token -> forward to upstream -> audit. This is the transport-agnostic core.
sessionCapabilityCache is the boundary where the Fast Capability Plane's latency split actually happens: MintRoot (real Vault call, paid once) only runs on a cache miss; every other call in the same session reuses the cached Root and pays only the cost of local attenuation.
sessionDBCache is the DB-credential analog of sessionCapabilityCache: the real Vault database secrets engine cost (a genuine CREATE ROLE round trip against the actual database) only gets paid once per session, per DB role - every other call in the same session reuses the already-open connection. Unlike the biscuit root (one keypair-derived object reused for any number of local attenuations), a real per-session dynamic DB credential means a real, separate connection pool per session - that's the actual, honest cost of genuine per-session dynamic secrets, not an oversight to optimize away.
Index ¶
- type Handler
- func (h *Handler) CompensateRun(ctx context.Context, runID, triggerReason string)
- func (h *Handler) CompleteApproval(ctx context.Context, p *approval.PendingApproval, req proxytypes.CallRequest) (proxytypes.CallResult, error)
- func (h *Handler) Handle(ctx context.Context, req proxytypes.CallRequest) (proxytypes.CallResult, error)
- func (h *Handler) LastPreImage(tool, resource string) (json.RawMessage, bool)
- func (h *Handler) ReplayHold(ctx context.Context, toolName string, args map[string]any, ...)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Handler ¶
type Handler struct {
Registry *registry.Registry
Policy *policy.Engine
Capability capability.RootIssuer
Approvals *approval.Store
Audit *audit.Logger
Metrics *metrics.Recorder
Risk risk.Scorer
Sagas *saga.Store
// ProvenanceJudge is nil unless cmd/rinwall sets it - the model-based
// provenance add-on is opt-in at the deployment level (a judge must be
// configured at all) AND per-tool (registry.Tool.ProvenanceJudge), on
// top of the caller supplying a context window for that specific call.
// All three gates must be open; any one absent means the existing
// self-reported-taint path is the only thing that runs, unchanged.
ProvenanceJudge provenance.Judge
// VisualProvenanceJudge is the screenshot-reasoning analog - nil
// unless cmd/rinwall configures it, opt-in the same three-gate way
// (deployment-level judge configured, registry.Tool.VisualProvenanceJudge
// set, AND the caller supplies both a screenshot and stated intent for
// this specific call). Checked at a call site independent of
// ReversibilityClass - see assessVisualProvenance's own doc comment for
// why that's deliberate, not an oversight matching ProvenanceJudge's
// A/C-only gate.
VisualProvenanceJudge visualprovenance.Judge
UpstreamBase string
HTTPClient *http.Client
// ReviewURL is where a person approves or cancels a held call, when
// cmd/rinwall knows it (zero-config mode: the loopback console). It
// only shapes the message the agent relays to its user; it is never a
// credential and grants nothing. Empty means "unknown" - a real
// deployment's console may sit behind the operator's own ingress, so
// the message names the reviewer role instead of guessing a URL.
ReviewURL string
// DBSecrets/DBDSNTemplate are nil/empty unless cmd/rinwall configures
// them - the real Vault database dynamic-secrets path (see
// dbquery.go) is opt-in the same way ProvenanceJudge is: a tool only
// takes this path at all if registry.Tool.IsDBBacked() is true, and
// that path only works if a minter is actually wired up here.
// DBDSNTemplate is a printf-style DSN with two %s placeholders
// (username, password) - everything else about the connection
// (host/port/database/sslmode) is fixed per deployment, only the
// per-session credential varies.
DBSecrets capability.DBCredentialMinter
DBDSNTemplate string
// AWSSecrets/AWSRegion are the AWS analog - nil/empty unless
// cmd/rinwall configures them, opt-in the same way. AWSRegion is the
// one piece of AWS SDK config that doesn't vary per credential (the
// bucket a tool reads is registry.Tool.S3Bucket, per-tool, since
// different aws-backed tools could plausibly target different
// buckets).
AWSSecrets capability.AWSCredentialMinter
AWSRegion string
// MCPUpstream is the client half of MCP-to-MCP passthrough - nil
// unless cmd/rinwall configures an upstream MCP server, opt-in the same
// way the others are, and only reached by a tool that sets mcp_tool.
// It holds the upstream's own credential, which is precisely what
// keeps that credential out of the agent's context - see mcpcall.go.
MCPUpstream *mcpclient.Client
// contains filtered or unexported fields
}
func (*Handler) CompensateRun ¶
CompensateRun compensates a run's executed Class A steps, grouped by RESOURCE rather than treated as one flat list. That grouping is the real dependency structure: two steps on the SAME resource may genuinely depend on each other (a later one may have been built on an earlier one's effect), so that pairing is still walked in strict REVERSE order, exactly as before. Two steps on DIFFERENT resources share nothing, so they have no real ordering dependency at all - each resource's chain is compensated independently and concurrently, and a failure in one resource's chain only stops THAT chain, never an unrelated one - see compensateResourceChain for why stopping still matters WITHIN one resource's chain.
Called from two places: Handle's wrapper, when a call within a tracked run comes back denied or errored, and internal/adapter/rest's /deny endpoint, when a reviewer explicitly denies a pending approval that belongs to a tracked run. Both call sites just need a runID - this method is a no-op if the run has nothing compensable, so callers never need to check first.
func (*Handler) CompleteApproval ¶
func (h *Handler) CompleteApproval(ctx context.Context, p *approval.PendingApproval, req proxytypes.CallRequest) (proxytypes.CallResult, error)
CompleteApproval is called by the /approvals/{id}/approve endpoint once the store has verified the hash still matches the original request.
func (*Handler) Handle ¶
func (h *Handler) Handle(ctx context.Context, req proxytypes.CallRequest) (proxytypes.CallResult, error)
Handle is the public entry point every adapter calls. It wraps handleInner with the saga compensation trigger: a denial or error within a tracked run (req.RunID set) walks that run's executed Class A steps in reverse and compensates them - see CompensateRun. handleInner has no knowledge of this; it just returns whatever the call's actual outcome was, same as every phase before this one.
func (*Handler) LastPreImage ¶
func (h *Handler) LastPreImage(tool, resource string) (json.RawMessage, bool)
LastPreImage exposes the most recent captured snapshot for a tool+resource - a diagnostic view. Automated compensation restores from the saga store's own copy, not from here.
func (*Handler) ReplayHold ¶
func (h *Handler) ReplayHold(ctx context.Context, toolName string, args map[string]any, requestedBy, delegatedRole, agentID string)
ReplayHold resumes a Tier-2 hold that survived a restart via approval.Store's persistence (see internal/approval/persist.go). cmd/rinwall calls this once at startup for every ResumableHoldIDs() entry, wired as the onFire callback to ScheduleResumedHold.