Security model
This page owns assets, threats, controls, limits, and non-goals.
ContainmentRule, AuditEvent, and Decision are defined on
Concepts.
Assets
Section titled “Assets”- Vendor credential bytes resolved from custody for one call
(
crates/mint-core/src/secret_store.rs:49-51). - The audit ledger: a durable, hash-chained record of every attempted
credential use (
crates/mint-core/src/ledger.rs:1-23). - Containment policy and capability declarations: which actor may reach
which service with which alias (
crates/mint-core/src/policy.rs:123-206).
Threats Mint defends against
Section titled “Threats Mint defends against”Mint is intended to prevent an agent from committing, printing, logging,
prompting with, or messaging a credential; returning it through a tool
result; and sending it to an unintended destination through Mint
(VISION.md:153-157). Concretely:
- Cross-alias injection: an actor allowed to call one host embedding a
header placeholder for a credential it was never granted. Blocked because
calling a host and injecting an alias are bound in the same rule
(
crates/mint-core/src/policy.rs:144-150), tested byexfil_cross_alias_injection_is_rejected(crates/mint-broker/tests/exfil.rs:62-116). A header value is always an injection slot: an unpermitted alias there denies the call. A body value or a query value is not — a well-formed placeholder for an unpermitted alias stays inert literal text and forwards unresolved instead of denying the call, exactly like a malformed placeholder (crates/mint-broker/src/proxy.rs:536-550,659-668, tested bycrates/mint-broker/tests/inert_unpermitted_placeholder.rs). - A credential riding alongside a placeholder: a header that resolves a
real placeholder but also carries a second, literal credential the agent
still holds. Caught by
require_placeholder's post-substitution scan, masking only what Mint itself just injected (crates/mint-broker/src/proxy.rs:460-486). - SSRF and private-network egress: a resolved target address inside
loopback, link-local, CGNAT, or other private ranges is refused unless
the matched rule explicitly opts in
(
crates/mint-broker/src/proxy.rs:169-183). - Open redirects and unpinned DNS: the proxy never follows redirects
(
crates/mint-broker/src/proxy.rs:726-732builds the client withredirect::Policy::none()) and resolves DNS to the exact addresses it authorized, rejecting any other address a resolver might substitute (crates/mint-broker/src/proxy.rs:1527-1538). - A vendor minting a credential Mint never resolved: an OAuth exchange
or "create API key" response. Caught by shape detection independent of
which field a vendor used
(
crates/mint-core/src/redact.rs:134-156). - A single hung upstream taking down the broker: the circuit breaker
opens after consecutive transport failures or 5xx responses and
short-circuits further calls to that host
(
crates/mint-broker/src/breaker.rs:6-17). - A tampered audit history: each record hashes its sequence number,
the previous record's hash, and its exact stored payload; any mutation,
reorder, or deletion breaks the chain on verification
(
crates/mint-core/src/ledger.rs:1-23). - A client disconnect abandoning a prepared effect: the effect continues on a detached task to its own terminal audit record — see Request flow.
Controls
Section titled “Controls”- Response scrubbing (
crates/mint-core/src/redact.rs): exact known secret values are removed byte-for-byte (crates/mint-core/src/redact.rs:26-33), then content-type-aware structural scrubbing runs by field name or value shape across JSON, form, SSE, and plain text (crates/mint-core/src/redact.rs:694-704). never_relay_credentials: for a token-minting endpoint, a matched rule can refuse to relay the response at all rather than trust a best-effort scrub (crates/mint-core/src/policy.rs:163-177).- Fail-closed audit: a prepared credentialed effect is durably recorded
before vendor contact. Early-denial paths attempt to append a denial
record before responding. If that append fails, the gate becomes
unhealthy and later effects fail closed, but the refused request may be
absent from the audit log
(
crates/mint-broker/src/audit_gate.rs:397-407). - Availability guards: a per-workload/service call-rate guard, kept
deliberately separate from containment policy and unable to express
methods, paths, credentials, spend, approvals, or task semantics
(
crates/mint-core/src/call_guard.rs:13-16). - Constant-time capability comparison: a dev/loopback capability token
is hashed at rest and compared as a fixed-size digest, not the raw bearer
(
crates/mint-core/src/capability.rs:16-28).
Limits, stated honestly
Section titled “Limits, stated honestly”Response scrubbing is layered rather than omniscient: exact resolved bytes
are removed, known shapes and sensitive fields add defense, and high-risk
routes can refuse to relay a response at all — this is not a general,
complete guarantee that every credential shape is caught
(VISION.md:166-171). The structural shape detectors cover only vendor
prefixes and forms Mint's fleet has observed; an opaque token from an
unrecognized vendor with no matching prefix or field name can pass a
best-effort scrub uncaught (crates/mint-core/src/redact.rs:146-155).
Mint also cannot eliminate every disclosure surface: bootstrap credentials
for tailnet enrollment, host deployment, and custody access exist before
Mint can mediate anything, and a compromised broker, host, custody root, or
external authority issuer can defeat the boundary (VISION.md:166-171). On
a single-user machine, same-user process isolation is not a credible
raw-secret boundary on its own — an agent that can inspect a child process
can often read its environment, files, or command line (VISION.md:173-178).
A body value or a query value is not an injection slot the way a header
is: a well-formed placeholder there resolves only when its alias is
permitted, and an unpermitted alias stays inert literal text instead of
denying the call (crates/mint-broker/src/proxy.rs:536-550,659-668;
crates/mint-broker/tests/inert_unpermitted_placeholder.rs). This closes
a false-positive failure mode — a caller merely quoting Mint's own
placeholder syntax as documentation or prompt text no longer gets denied
— but it does not close the matching risk in the other direction: a
permitted alias's placeholder text embedded anywhere in a body or a
query value still resolves and still injects the real secret at that
location, whether the caller meant it as an injection request or as
prose. Mint does not yet distinguish those two intents for a permitted
alias (tracked in the dc97a88 commit message as
mint-permitted-alias-prose-secret-injection).
Non-goals
Section titled “Non-goals”Mint does not decide whether an otherwise valid action is wise. A bad pull
request, wrong refund, excessive purchase, destructive but externally
granted deployment, or data exfiltration through a legitimately permitted
API belongs to the authority and execution layers, not to Mint
(VISION.md:159-164). Mint is not a general exfiltration-prevention
system: it defends the confidentiality of the credential bytes it brokers,
not the semantic content of what an already-authorized call sends or
receives. Budget, Approval, and semantic Permission are deliberately
not Mint product primitives (VISION.md:124-131). Mint may mechanically
validate an opaque, externally issued grant before using a credential, but
that is enforcement at the containment boundary, not Mint deciding what an
agent is authorized to do (VISION.md:74-78).
Source map
Section titled “Source map”| Claim | Source | Lines |
|---|---|---|
| Threat model, honestly | VISION.md | 151-178 |
| Cross-alias injection guard | policy.rs | 144-150 |
| Cross-alias injection test | exfil.rs | 62-116 |
| require_placeholder mixed-header re-check | proxy.rs | 460-486 |
| Private-network egress block | proxy.rs | 169-183 |
| No redirect following | proxy.rs | 726-732 |
| Pinned DNS resolver | proxy.rs | 1527-1538 |
| Header alias not permitted denies the call | proxy.rs | 403-421 |
| Unpermitted alias in a body value left inert | proxy.rs | 536-550 |
| Unpermitted alias in a query value left inert | proxy.rs | 659-668 |
| Inert-mention regression coverage, header boundary pinned | inert_unpermitted_placeholder.rs | 1-18 |
| Shape-based credential detection | redact.rs | 134-156 |
| Circuit breaker | breaker.rs | 6-17 |
| Hash-chained ledger | ledger.rs | 1-23 |
| Exact-value redaction | redact.rs | 26-33 |
| Structural scrub dispatch | redact.rs | 694-704 |
| never_relay_credentials | policy.rs | 163-177 |
| Availability guard, separate from policy | call_guard.rs | 13-16 |
| Constant-time capability compare | capability.rs | 16-28 |
| Scrubbing is layered, not omniscient | VISION.md | 166-171 |
| Shape detector coverage is bounded | redact.rs | 146-155 |
| Same-user isolation is not a credible boundary | VISION.md | 173-178 |
| Permitted-alias prose injection is an open risk, not solved | inert_unpermitted_placeholder.rs | 1-18 |
| Exfiltration through a permitted API is out of scope | VISION.md | 159-164 |
| Budget/Approval/Permission are not Mint primitives | VISION.md | 124-131 |
| External grant enforcement, not authorization | VISION.md | 74-78 |