The request path
Updated July 22, 2026
The request path
Hangar is a proxy. A client’s tools/call goes in one side, the upstream MCP
server’s result comes back the other, and every control Hangar enforces gets its
turn on the same path the call already travels. There is no side channel and no
background pass. Governance is whatever fires between the client and the upstream,
in order, synchronously, on the way through.
This page is that path, drawn once. Follow the request down the spine, watch each control fire at the seam, and follow the result back up.
The stages, one caption each
Client / agent. A tool call enters carrying a token. Nothing about it is trusted yet.
1 · Identity. The front door authenticates the caller and resolves it to a tenant and principal. This binding is what every later control keys off. An unauthenticated caller never reaches stage 2 — it is rejected at the door.
2 · Contract. The call is checked against the tool-schema digest pinned for the exact contract the caller was authorized for — resolved at invoke time from the tool-projection registry. If the upstream tool has drifted from that pinned digest, the path fails closed rather than invoke a contract nobody signed off on.
3 · Egress policy (L7). This is the semantic gate. Tool names are matched by
glob in a fixed precedence — deny beats requireApproval beats allow beats the
policy’s default action — and arguments are scanned for configured secret
patterns and an oversized payload. A secret or an outsized payload denies the
call even when the tool name is allowed: deny always wins. requireApproval is
a hard gate — the call is blocked pending out-of-band approval. It is
enforcement, not an interactive queue; the request stops, it does not hang waiting
for a click.
Whether stage 3 blocks at all is opt-in. A policy’s spec.mode defaults to
Audit — every verdict above is computed and recorded, but nothing is
refused; the seam observes. Only under mode: Enforce do deny and
requireApproval actually stop the call. The blocking picture on this page is the
Enforce-mode picture; Audit is the adoption path you start on.
L3/L4 network backstop. Underneath the L7 seam, the operator compiles the same
policy into a NetworkPolicy (or a CiliumNetworkPolicy for FQDN upstreams) that
default-denies the server’s egress to everything but the allow-listed hosts. This
is the layer that makes the whole path enforcement rather than a suggestion: a
policy without the backstop is bypassable, and Hangar fails it closed rather than
open when the backstop can’t be applied.
Upstream MCP server. Only an allowed, in-contract call arrives here, relayed untouched. Hangar does not run the work — it relays and governs. The upstream owns execution.
The return trip. The result comes back up the same seam: output is scanned and
secret values are redacted, reusing the redactor’s deterministic value-patterns —
the same ones argument scanning refuses on the way in, plus one inbound-only PEM
addition the redactor does not carry — and the governed result is written to the
append-only provenance chain. So most of what the redactor masks on the way out is
what a policy refuses on the way in — largely one set of rules, both directions. (A
call the policy denies raises EgressPolicyDeniedError before it reaches the wire,
short-circuiting this return trip.)
Why it’s drawn as one line
Every control lives on the call path and nowhere else. There is no anomaly score being computed in the background, no separate policy engine the request detours through, no asynchronous approval the call waits on. The verdicts are deterministic functions of the request, the pinned contract, and the policy — which is what makes the path auditable: the same inputs always produce the same verdict, and every verdict leaves a line in the chain. Governance is credible here precisely because it is thin. It binds where the call already flows, and the call flows through exactly one seam.
Grounded in ADR-013 (the egress
enforcement model) and the MCPEgressPolicy guide. Everything is MIT and
self-hosted — no SaaS tier.