Design rationale
ContainmentRule, WorkloadIdentity, and execution mode are defined on
Concepts. This page explains why each design choice
looks the way it does, not just what it does.
Non-possession over reduced-consequence mitigation
Section titled “Non-possession over reduced-consequence mitigation”The fleet leaked API keys three times in 72 hours by cat/grep-ing
secret files into permanently indexed transcripts, and a later op://
conversion still passed reference strings as bearer tokens while the op
CLI hung machine-wide (VISION.md:12-16). Prompt discipline, secret
scanning, short TTLs, and narrower vendor permissions reduce the
consequences of a leak; they do not make possession itself safe
(VISION.md:18-21). Mint's answer is structural rather than
behavioral: keep the bytes out of every agent-accessible surface, so there
is nothing left for a leak, a scan miss, or a permission gap to expose.
That is why mint-mcp and the CLI's observability commands read
declarations and audit history but never construct a SecretStore
(crates/mint-mcp/src/lib.rs:1-9,103-106).
HTTP proxy as the default execution mode
Section titled “HTTP proxy as the default execution mode”The egress credential proxy is the default; a narrowly typed effect or
protocol gateway is justified only by a discovered local path that cannot
use the proxy while preserving non-possession
(VISION.md:103-106). The proxy generalizes to any vendor that accepts a
credential in a header or body: authenticate, evaluate policy, resolve the
placeholder, forward, scrub, audit — one code path for every HTTP-shaped
integration (crates/mint-broker/src/lib.rs:1-12). A typed action is the
deliberately expensive exception, built only when a vendor's own response
mints a transferable session handle the generic proxy cannot safely relay
— see below.
External semantic authority, not policy ownership
Section titled “External semantic authority, not policy ownership”Mint enforces containment; it does not decide whether an action is
appropriate. Deciding whether to run a task, who approves it, and how much
it may spend belongs to an external authority system or operator, not to
Mint (VISION.md:65-76). Mint may authenticate a caller and mechanically
validate an opaque, externally issued grant before using a credential —
that is enforcement at the containment boundary, not Mint deciding what an
agent is authorized to do (VISION.md:74-78). Destination and alias
binding stay inside Mint regardless, because sending a secret to the wrong
peer is a disclosure failure, not merely a permission failure
(VISION.md:77-78). A Powder card or claim may cause an external authority
service to issue a grant, but Mint does not infer semantic permission from
ticket lifecycle (VISION.md:139-141). This split is why Budget,
Approval, and semantic Permission are deliberately absent from Mint's
own product primitives (VISION.md:124-131) — an external grant, when one
exists, is validated mechanically at the boundary, never reasoned about
by Mint's own policy engine.
Loopback-only shared-secret auth
Section titled “Loopback-only shared-secret auth”SharedSecretAuth checks a bearer capability token against
X-Mint-Capability, but its authenticate implementation refuses any
caller whose peer address does not canonicalize to loopback before it even
looks at the header (crates/mint-broker/src/auth.rs:84-86). A bearer
token is a boundary a leaked transcript or a curl from any tailnet peer
defeats identically, so it was demoted to a dev/loopback-only path; the
loopback check is enforced in the authenticator's own type, not merely by
which auth mode a config happens to select — a misconfigured deploy that
still wires it up cannot accidentally accept a non-local caller
(crates/mint-broker/src/auth.rs:50-59). Production instead resolves
identity from the caller's real tailnet peer address via
TailnetWhoisAuth: a peer's source IP cannot be forged over a completed
WireGuard handshake, while an X-Mint-* identity header is just bytes any
TCP-reachable client can set (crates/mint-broker/src/tailnet.rs:1-20).
shared_secret_auth_from_a_non_loopback_peer_is_refused and
shared_secret_auth_from_loopback_still_works pin both halves of this
contract (crates/mint-broker/tests/auth_boundary.rs:73-109,111-144).
Audit prepare before any vendor contact
Section titled “Audit prepare before any vendor contact”AuditGate::prepare durably records an effect as Prepared before
proxy_handler ever spawns the task that dials a vendor
(crates/mint-broker/src/proxy.rs:261-267,
crates/mint-broker/src/audit_gate.rs:268-270). This ordering is what
makes a crash, an aborted task, or a dropped client connection safe to
reason about afterward: an effect that reached prepare but never reached
a terminal record is unresolved, not silently lost, and startup recovery
can find it by scanning for exactly that gap
(crates/mint-broker/src/audit_gate.rs:79-82). Preparing first, contacting
the vendor second, is what turns "did this call happen" into a question
the ledger can always answer — see
Request flow for the full sequence this enables.
One narrow typed action
Section titled “One narrow typed action”Typed actions are not a general command-runner family; the registry is
fixed at compile time (crates/mint-core/src/typed_action.rs:14-21), and
exactly one action ships today: firecrawl.interact_prompt
(crates/mint-core/src/typed_action.rs:56-67). It exists because
Firecrawl's own /interact response mints a transferable cdpUrl,
liveViewUrl, and interactiveLiveViewUrl alongside its semantic answer —
fields that are the vendor session handle, not an incidental leak a
response scrub could catch after the fact
(crates/mint-core/src/typed_action.rs:1-12). Mint owns the whole scrape →
interact → stop session lifecycle inside one Mint-mediated call, so no
caller ever receives a live handle to hold onto
(crates/mint-broker/src/typed_effect.rs:18-22). The action's input schema
is deliberately prompt-only: Firecrawl's code mode accepts arbitrary
Playwright/Python and returns raw stdout/stderr/result, which would
make this a generic command runner — an explicit non-goal the input schema
enforces by having no code field at all
(crates/mint-core/src/typed_action.rs:48-55). A typed-action request
follows the same admission order as the proxy — audit-ready check,
authenticate, evaluate_tool, prepare, detached task — so it can never
skip a gate the proxy enforces (crates/mint-broker/src/typed_effect.rs:50-56).
Source map
Section titled “Source map”| Claim | Source | Lines |
|---|---|---|
| Why this exists (three leaks in 72h) | VISION.md | 12-21 |
| mint-mcp never touches SecretStore | lib.rs | 1-9 |
| HTTP proxy is the default execution mode | VISION.md | 101-106 |
| Broker data-plane summary | lib.rs | 1-12 |
| External semantic authority | VISION.md | 65-78 |
| Powder records work, not authority | VISION.md | 139-141 |
| Budget/Approval/Permission excluded | VISION.md | 124-131 |
| SharedSecretAuth loopback enforcement | auth.rs | 84-86 |
| SharedSecretAuth demoted to dev/loopback | auth.rs | 50-59 |
| TailnetWhoisAuth rationale | tailnet.rs | 1-20 |
| Loopback/tailnet auth boundary tests | auth_boundary.rs | 73-109, 111-144 |
prepare before vendor contact | proxy.rs | 261-267 |
AuditGate::prepare | audit_gate.rs | 268-270 |
| Startup recovery of unresolved effects | audit_gate.rs | 79-82 |
| Typed-action registry is fixed | typed_action.rs | 14-21 |
| Exactly one action registered | typed_action.rs | 56-67 |
| Why firecrawl.interact_prompt exists | typed_action.rs | 1-12 |
| Whole session lifecycle owned by Mint | typed_effect.rs | 18-22 |
Prompt-only input, no code field | typed_action.rs | 48-55 |
| Typed action mirrors proxy admission order | typed_effect.rs | 50-56 |