Request flow
This page owns the request sequence for POST /proxy/{scheme}/{host}/{*rest}
(crates/mint-broker/src/proxy.rs:29-35). ContainmentRule, Decision,
and AuditEvent are defined on Concepts.
Sequence
Section titled “Sequence”The initial readiness check can return 503 without an event because the
audit sink is unavailable; no effect is attempted. After readiness passes,
every early-return branch below attempts to append an AuditEvent before
responding. If that append fails, the gate becomes unhealthy and later
effects fail closed, but that refused request may be absent from the audit
log (crates/mint-broker/src/audit_gate.rs:397-407).
sequenceDiagram
autonumber
participant Agent
participant Handler as proxy_handler
participant Gate as AuditGate
participant Policy
participant Task as run_prepared_effect<br/>(detached task)
participant Store as SecretStore
participant Vendor
Agent->>Handler: HTTP request, placeholder in header/body
Handler->>Gate: is_ready()
alt gate not ready (draining or unhealthy)
Handler-->>Agent: 503, no effect attempted (proxy.rs:48-64)
else ready
Handler->>Handler: reject_ambiguous_ingress(uri, headers)
alt absolute-form target, path traversal, control byte
Handler->>Gate: append(Deny, ingress_boundary_rejected)
Handler-->>Agent: 400 bad request (proxy.rs:72-82)
else ingress clean
Handler->>Handler: authenticate(peer, headers) on spawn_blocking
alt no/invalid capability, whois miss, task panic
Handler->>Gate: append(Deny, auth_failed)
Handler-->>Agent: 401 (proxy.rs:100-124)
else actor resolved
Handler->>Policy: evaluate(EvalRequest)
alt Decision::Deny or scheme not permitted
Handler->>Gate: append(Deny, policy/scheme reason)
Handler-->>Agent: 403 (proxy.rs:142-158)
else Decision::Allow
Handler->>Handler: resolve_target(host); block private egress unless opted in
alt DNS fails, or private/loopback IP without allow_private_network
Handler->>Gate: append(Deny, target_resolution_failed / private_egress_not_permitted)
Handler-->>Agent: 502 / 403 (proxy.rs:160-183)
else target resolved
Handler->>Handler: call_guard.check_and_increment(actor, host)
alt call-guard window exceeded
Handler->>Gate: append(Deny, call_guard_exceeded)
Handler-->>Agent: 429 (proxy.rs:207-218)
else within guard
Handler->>Handler: breaker.check(host)
alt circuit open
Handler->>Gate: append(Deny, circuit_breaker_open_until)
Handler-->>Agent: 503 (proxy.rs:221-226)
else circuit closed
opt rule sets require_placeholder
Handler->>Handler: scan query/body for a literal credential
alt literal credential present, no placeholder
Handler->>Gate: append(Deny, require_placeholder_violation)
Handler-->>Agent: 403 (proxy.rs:228-259)
end
end
Handler->>Gate: prepare(event)
alt prepare fails (audit sink down)
Handler-->>Agent: 503, no effect attempted (proxy.rs:261-267)
else prepared, durably recorded
Handler->>Task: spawn, latched on a oneshot
Handler->>Gate: track(effect_id, abort_handle)
Note over Handler,Task: latch releases only after track()<br/>succeeds -- a concurrent drain can<br/>never let the task reach the vendor<br/>after a forced terminal (proxy.rs:279-314)
Note over Task: an unpermitted alias in a body or query value<br/>stays inert literal text, never resolved (proxy.rs:536-550,659-668)
Task->>Store: resolve each header alias, and each permitted body/query alias, bounded timeout
alt header alias not permitted by rule
Task->>Gate: finalize_prepared(Deny, alias_not_permitted_by_rule)
Task-->>Handler: 403 (proxy.rs:403-421)
else a resolve call fails or times out
Task->>Gate: finalize_prepared(Deny, secret_resolve_failed)
Task-->>Handler: 502 (proxy.rs:422-437)
else every alias resolved
Task->>Vendor: forward with secret substituted, Accept-Encoding: identity
alt connect/TLS/timeout error
Task->>Gate: finalize_prepared(Indeterminate, upstream_error/upstream_timeout)
Task-->>Handler: 502 / 504 (proxy.rs:768-796)
else response received
Task->>Task: decode body, scrub_response_body(), scrub headers
alt unsupported/stacked encoding, oversized, unreadable
Task->>Gate: finalize_prepared(Deny/Indeterminate, response_* reason)
Task-->>Handler: 502 / 504 (proxy.rs:798-1128)
else body decoded and scrubbed
alt rule sets never_relay_credentials AND a credential is still detected
Task->>Gate: finalize_prepared(Deny, credential_in_response_denied)
Task-->>Handler: 502, response withheld entirely (proxy.rs:1138-1161)
else clean
Task->>Gate: finalize_prepared(Observed, Allow)
Task-->>Handler: 200 + scrubbed body/headers (proxy.rs:1163-1204)
end
end
end
end
Handler-->>Agent: relay Task's response
end
end
end
end
end
end
end
end
Fail-closed branches
Section titled “Fail-closed branches”The initial audit-readiness rejection is a third shape: it returns 503
without an audit write because the sink is unavailable, and no effect is
attempted (crates/mint-broker/src/proxy.rs:48-64). After readiness passes,
every alternative above ends with either a synchronous AuditGate::append
for a decision made before prepare
(crates/mint-broker/src/audit_gate.rs:261-267), or
append_terminal_failure/finalize_prepared for a decision made after it
(crates/mint-broker/src/proxy.rs:1338-1349). A
never_relay_credentials rule additionally refuses to relay
the response body at all rather than trust a best-effort scrub for a
token-minting endpoint (crates/mint-core/src/policy.rs:163-177, enforced
at crates/mint-broker/src/proxy.rs:1138-1161). An unpermitted alias in a
header value is the only placeholder occurrence that denies the call; the
same alias merely quoted in a body or a query value is left as inert
literal text and never reaches this fail-closed machinery at all
(crates/mint-broker/src/proxy.rs:536-550,659-668, see
Security for the boundary this draws).
Cancellation safety
Section titled “Cancellation safety”axum/hyper can drop a handler's future the instant the client
disconnects, even after prepare() has durably recorded the effect
(crates/mint-broker/tests/cancellation_safety.rs:1-11). proxy_handler
defends against this by spawning run_prepared_effect as its own detached
task, latched on a oneshot channel so it cannot start forwarding to a
vendor before AuditGate::track has installed its abort handle
(crates/mint-broker/src/proxy.rs:279-314). The handler only .awaits
the task to relay its result if the connection is still there to receive
it; the task itself runs to its own terminal record regardless
(crates/mint-broker/src/proxy.rs:315-333). The regression test
client_disconnect_after_prepare_does_not_abandon_the_effect drives a real
TCP connection, drops it before reading any response, and asserts the
effect still reaches exactly one terminal record
(crates/mint-broker/tests/cancellation_safety.rs:72-171).
Graceful shutdown closes the remaining gap: SIGTERM/SIGINT stops new
admission immediately, waits up to a bounded drain_bound for in-flight
effects to reach a terminal record, and force-terminalizes anything still
active as Indeterminate/Deny — never a fabricated success
(crates/mint-broker/src/lib.rs:333-344,
crates/mint-broker/src/audit_gate.rs:329-330).
Source map
Section titled “Source map”| Claim | Source | Lines |
|---|---|---|
| Handler audit-readiness scope | proxy.rs | 29-35 |
| Audit-not-ready branch | proxy.rs | 48-64 |
| Ingress boundary rejection | proxy.rs | 72-82 |
| Authentication branch | proxy.rs | 84-124 |
| Policy/scheme deny | proxy.rs | 134-158 |
| Target resolution / private egress | proxy.rs | 160-183 |
| Call guard | proxy.rs | 200-219 |
| Circuit breaker | proxy.rs | 221-226 |
| require_placeholder violation | proxy.rs | 228-259 |
prepare and detached spawn | proxy.rs | 261-314 |
| Header alias not permitted → 403 | proxy.rs | 403-421 |
| Secret resolve failure → 502 (same pattern repeats for the body loop at L551-L570 and the query loop at L669-L688) | proxy.rs | 422-437 |
| Unpermitted alias in a body value left inert, no resolution attempted | proxy.rs | 536-550 |
| Unpermitted alias in a query value left inert, no resolution attempted | proxy.rs | 659-668 |
| Upstream transport error/timeout | proxy.rs | 768-796 |
| Response decode/scrub-safety failures | proxy.rs | 798-1128 |
| never_relay_credentials denial | proxy.rs | 1138-1161 |
| Terminal Allow write and response | proxy.rs | 1163-1204 |
append_terminal_failure | proxy.rs | 1338-1349 |
| Header vs. body/query placeholder split, rationale | inert_unpermitted_placeholder.rs | 1-18 |
| Header boundary regression test | inert_unpermitted_placeholder.rs | 346-389 |
AuditGate::prepare/append | audit_gate.rs | 261-270 |
force_terminalize_active | audit_gate.rs | 329-330 |
| Cancellation-safety test rationale | cancellation_safety.rs | 1-11 |
| Cancellation-safety test | cancellation_safety.rs | 72-171 |
| Graceful drain, force-terminalize | lib.rs | 333-344 |