Mitrity LLM Gateway
The Mitrity LLM Gateway checks the requests your agents send to LLM providers. You run it in your own cluster, and you point each agent's provider base_url at the gateway instead of at the provider.
For each request, the gateway:
- accepts the agent's TLS connection, using its own certificate;
- runs the MITRITY decision engine on the parsed prompt;
- redacts sensitive data in the request body, in place, if a DLP rule matches;
- opens a new TLS connection to the real provider and forwards the request;
- scans the model's response, including streamed responses, before the agent sees it.
This is the fourth deployment surface, next to Mitrity Gateway, Mitrity MCP Sidecar and the Mitrity Mesh Authorizer. It is the same decision engine at a different decision point. The Mesh Authorizer cannot rewrite a request body on the wire, but the Mitrity LLM Gateway owns the body. Matched secrets and PII are scrubbed before the prompt ever leaves your cluster, so redaction happens on the wire, in both directions.
Use it when your agents call Anthropic, OpenAI, Azure OpenAI, Vertex AI, Amazon Bedrock or an OpenAI-compatible host directly (not through an MCP tool), and you want mission-scope, threat-intel and DLP governance on that traffic. You also get usage and cost visibility per agent and per model.
What it governs
Every request to a configured provider path runs the same decision engine that powers the other MITRITY surfaces:
| Capability | Status at the LLM Gateway |
|---|---|
| Prompt DLP: block | Enforced (403; the prompt never reaches the provider) |
| Prompt DLP: redact | Enforced as wire-redaction: matched content is scrubbed from the request body before it is forwarded |
| Response DLP: block / redact | Enforced on the text the model generates, before it reaches the agent. Streamed responses use the holdback-window scan (see Streaming holdback). Thinking and reasoning text is inspected on every leg and never rewritten: a redact match denies instead (see Response DLP and streaming) |
| Intent / mission-scope | Enforced: each prompt egress is evaluated as an outbound action (llm:send:<provider>:<model>) against the agent's declared mission |
| Threat-intel | Enforced on the prompt content and the provider/model destination |
| Injection scan | Per policy: llm_injection_mode decides. alert (the default) records the event and lets the call proceed, enforce blocks the call, and off skips the scan for prompts. See Prompt injection on prompts |
| Model access: allowlist / denylist | Enforced: a policy can restrict which providers/models an agent may call, and an off-allowlist call is denied (403). See Guardrails |
| Per-call token cap | Enforced: a policy can cap the output tokens a single call may request, and an over-cap call is denied |
| Cost budget: token / USD | Enforced: a per-policy budget over a rolling window. When it is exceeded, the gateway blocks (403), holds for approval (409), or alerts-only |
| Rate limit: requests/min | Enforced: a per-policy cap on LLM calls/min per agent. Over-limit calls get 429 |
| LLM usage & BOM | Recorded: provider, model, token counts and redaction counts, per agent and per call (metadata only; see LLM usage, cost & BOM) |
| Identity | Fail-closed: a request with no resolvable identity is always denied |
| Provider keys | Brokered, per provider: with auth: brokered, the agent holds no provider key. The gateway injects one that it resolves from the credential broker per call, and strips whatever credential the agent presented. See Agents hold no provider keys |
A request path with no configured provider returns 404. It is never forwarded anywhere.
Prompt injection on prompts
The injection scan runs on every prompt. The policy's llm_injection_mode decides what a hit does:
enforceblocks the model call;alert, the default, lets the call proceed and records the event;offskips the scan for prompts.
Tool calls are governed by the policy's injection_mode, and this setting does not affect them.
The default is alert for two reasons. An injection in a prompt does no harm by itself: the harm is the tool call it induces, and that stays enforced. And agents that read logs, web pages or tickets legitimately see injection-shaped text.
Setting injection_mode to off turns the scan off everywhere, prompts included.
Guardrails: model access, cost & rate
Beyond inspecting each prompt, the gateway enforces per-policy guardrails on LLM traffic. You configure all of them in the dashboard, under Policies → (a policy) → LLM Guardrails. They apply to every agent bound to that policy, and the gateway enforces them before the call reaches the provider:
-
Model allowlists / denylists: restrict which providers and models an agent may call. For example, allow only
anthropic:claude-*, denyazure-openai:gpt-4o, or allowvertex:gemini-2.5-*. You author them asllm:send:<provider>:<model>allow/deny rules. The policy's default action decides between "allow all except denied" and "deny all except allowed." An off-allowlist call is denied with a provider-shaped 403. -
Per-call token caps: cap the output tokens a single call may request (
max_output_tokens). A request over the cap is denied before it reaches the provider. -
Cost budgets (token / USD): a per-policy budget over a rolling day / week / month window, measured from actual usage. Set a token budget (exact), a USD budget (best-effort, priced from the model catalog), or both. Add a warn threshold (default 80%) and an exceeded action:
- block: over-budget calls are denied (403);
- hold: over-budget calls are held for approval (409), and you approve them to keep spending;
- alert-only: calls proceed, and you are notified.
On the policy, the dashboard shows a live burn-down (used vs limit, % consumed, current window). Spend alerts fire to Slack/email once at the warn threshold, and once when the budget is exceeded.
-
Rate limits: a per-policy cap on LLM calls per minute per agent. Requests over the limit get a
429.
Budgets and rate limits fail open: an agent with no budget or limit configured is never blocked, because cost control is opt-in. Identity and DLP, by contrast, fail closed.
These fine-grained controls are specific to the LLM Gateway, which parses the request and response body. The Mesh Authorizer can enforce a coarse egress allowlist to provider hosts (for example, block
api.openai.comentirely), but it cannot see the model, the token counts or the spend. For model / token / budget / rate governance, route LLM traffic through the gateway.
Supported providers
| Provider | API | Gateway path |
|---|---|---|
| Anthropic | Messages API | /v1/messages |
| OpenAI | Chat Completions API | /v1/chat/completions |
| Azure OpenAI / AI Foundry | Chat Completions API | /openai/deployments/{deployment}/chat/completions and /openai/v1/chat/completions |
| Google Cloud Vertex AI | Gemini generateContent API | /v1/projects/{project}/locations/{location}/publishers/{publisher}/models/{model}:generateContent and :streamGenerateContent |
| Amazon Bedrock | OpenAI-compatible Chat Completions API | /openai/v1/chat/completions (see Amazon Bedrock) |
| Any OpenAI-compatible host (Mistral, Groq, Together, OpenRouter, vLLM, ...) | Chat Completions API | <mount>/chat/completions, forwarded to the host's own <path_prefix>/chat/completions; both are /v1 by default (see OpenAI-compatible providers) |
Anthropic and OpenAI are configured by default. Remove one from the configuration to 404 its path.
Azure OpenAI, Vertex AI and OpenAI-compatible hosts have no default upstream: you name your own resource, regional endpoint or host. Bedrock's regional endpoint follows from the region you configure.
Each upstream URL must be https://, because the gateway opens a fresh TLS connection toward the provider. You can point an upstream at a regional or proxy endpoint, if your provider contract requires one.
Two providers that could answer the same path are refused when the gateway starts, rather than guessed. That happens with:
- OpenAI and a compatible host, both on
/v1; - Azure's v1 surface and a compatible host on
/openai/v1; - Azure's v1 surface and Bedrock.
Give the compatible host a mount of its own, and keep Azure on its deployment surface next to Bedrock.
A policy names a model by its provider key: anthropic, openai, azure-openai, vertex, aws-bedrock or openai-compatible. The same key appears on every usage record, in the LLM BOM and in the price catalog, so vertex:gemini-2.5-pro means the same thing wherever you see it.
Agents keep their own provider API key. The gateway forwards it verbatim to the provider, and only changes where the traffic flows. To point an agent at the gateway, you change its base URL and make it trust the gateway's CA:
# Anthropic SDKs:
export ANTHROPIC_BASE_URL=https://<gateway-host>:8443
# OpenAI SDKs (note the /v1 -- OpenAI clients append /chat/completions):
export OPENAI_BASE_URL=https://<gateway-host>:8443/v1
# An OpenAI-compatible host: the gateway plus the mount you configured for it
export OPENAI_BASE_URL=https://<gateway-host>:8443/mistral/v1
# Amazon Bedrock through an OpenAI SDK: the gateway plus /openai/v1, with your
# Bedrock API key as the bearer token
export OPENAI_BASE_URL=https://<gateway-host>:8443/openai/v1
export OPENAI_API_KEY=$AWS_BEARER_TOKEN_BEDROCK
# Azure OpenAI SDKs: pass the gateway as the endpoint
# AzureOpenAI(azure_endpoint="https://<gateway-host>:8443", ...)
# Trust the gateway CA (or use your SDK's CA option):
export SSL_CERT_FILE=$PWD/mitrity-llm-gateway-ca.crt
# Vertex AI (google-genai): keep vertexai=True with your project and location;
# the SDK builds the full /v1/projects/.../models/{model}:generateContent path
# and sends ?alt=sse on streams, so only the base URL changes.
from google import genai
from google.genai import types
client = genai.Client(
vertexai=True, project="my-gcp-project", location="europe-north1",
http_options=types.HttpOptions(base_url="https://<gateway-host>:8443"),
)
Azure OpenAI and AI Foundry
Azure has no default upstream, because every subscription has its own resource. So the upstream URL is required. It must be an *.openai.azure.com or *.services.ai.azure.com host, unless you explicitly allow a private endpoint. Both surfaces are served by default; enable only the one you use if you would rather the other return 404. The api-version query parameter rides through untouched, as does every other query parameter.
Map each deployment to the model it serves. An Azure deployment name is a label you chose, and the model behind it appears nowhere in the request. Without a mapping, the gateway cannot name the model. A policy rule that names models, such as a model allowlist or a price-catalog entry, then has nothing to match. So the gateway resolves the deployment to a model before it judges the call:
providers:
azure-openai:
upstream_url: "https://your-resource.openai.azure.com"
deployments:
gpt-4o-prod: "gpt-4o"
gpt-4o-mini-batch: "gpt-4o-mini"
A call to a deployment that is not in the map is denied (403). The refusal is recorded on the audit trail with the deployment name, so you can see exactly what to add. on_unknown_deployment: allow forwards those calls instead. Use it only while you finish the map, because a call whose model the gateway cannot name is a call your model allowlists do not restrict.
The dashboard, the usage records, the LLM BOM and every policy rule see the resolved model: an Azure call through gpt-4o-prod is governed and priced as azure-openai:gpt-4o. The request body that reaches Azure is the body your agent wrote, minus anything DLP redacted. The gateway never adds a field that your SDK did not send.
Copilot Studio and Microsoft 365 Copilot are not proxied here. Their models run inside Microsoft, so there is no customer-controlled hop for the gateway to sit on. Azure OpenAI / AI Foundry traffic is protected inline at the gateway. Copilot Studio and Microsoft 365 Copilot agents are discovered, inventoried and posture-assessed through the Microsoft 365 connector instead.
Vertex AI
Vertex AI is governed on the Gemini generateContent and streamGenerateContent methods. The model is read from the request path (.../models/gemini-2.5-pro:generateContent). So a policy rule such as llm:send:vertex:gemini-2.5-* applies before the call is forwarded, and the usage record names vertex:gemini-2.5-pro. Your agents keep their own Google credentials, and the bearer token is forwarded as-is.
There is no default upstream: set the regional endpoint that you call today. A shared gateway can be pinned to one project and region. A request path that names any other project or region is answered 404 and is never forwarded.
providers:
vertex:
upstream_url: "https://europe-north1-aiplatform.googleapis.com"
project: "my-gcp-project" # optional pin
location: "europe-north1" # optional pin
Two things to know about Vertex traffic:
- Streaming works best with
alt=sse. Every Google SDK sends it, and the gateway then applies the streaming holdback scan frame by frame. Withoutalt=sse, Vertex returns one JSON array instead of an event stream. The gateway buffers that body and scans it whole before delivering it. The response is still governed and still redacted, but without the time-to-first-token benefit. - Thinking tokens count as output. Gemini bills the tokens a model spends reasoning as output tokens, and so does the gateway.
output_tokenson a Vertex usage record is the sum of the candidate and thinking token counts that Vertex reports, and your token budgets and the estimated cost are measured against it.
rawPredict (partner models) and Vertex's own OpenAI-compatible endpoint are not served and return 404.
Amazon Bedrock
Bedrock is governed on its OpenAI-compatible surface: /openai/v1/chat/completions, authenticated with a Bedrock API key as the bearer token (AWS_BEARER_TOKEN_BEDROCK). Traffic on that surface gets the same governance as OpenAI's own. The prompt is decomposed, scanned and redacted in place, the response is scanned, and the call is metered. Your agents keep their own Bedrock API key, and the gateway forwards it as-is.
providers:
aws-bedrock:
region: "us-west-2" # the regional endpoint is derived from it
auth: "passthrough"
Models are governed by their base id. Bedrock model ids carry a version suffix with a colon (anthropic.claude-sonnet-4-5-20260514-v1:0), and a cross-region inference profile adds a region prefix (us., eu., apac., us-gov.). The gateway strips the prefix for the verdict, the usage record and the price catalog. A call to us.anthropic.claude-sonnet-4-5-20260514-v1:0 is governed as aws-bedrock:anthropic.claude-sonnet-4-5-20260514-v1:0, so one rule covers every regional profile of a model. The gateway still forwards the body with the id your agent wrote. Every usage record from Bedrock also carries how the gateway authenticated (passthrough).
What this release does not cover. Bedrock's native Converse / ConverseStream and InvokeModel surfaces sign the request body with SigV4. These are the surfaces that boto3's converse() and LangChain's ChatBedrock call. A gateway that redacts such a body must re-sign it with a credential of its own, and that adapter has not shipped. So those surfaces and the resign mode are recognized in configuration, and refused at startup with a message that says so. An agent that calls Bedrock through an OpenAI SDK against the compatible endpoint is governed today. An agent on boto3 or ChatBedrock is not.
Bedrock and Azure's v1 surface publish the same path. With both configured, the gateway refuses to start, so keep Azure on its deployment surface.
OpenAI-compatible providers
Most model hosts outside the big four publish the OpenAI Chat Completions wire format under their own origin: Mistral, Groq, Together, OpenRouter and every self-hosted vLLM. The gateway governs them all as one provider, openai-compatible. They get the same prompt decomposition, DLP redaction, streaming scan and usage metering as OpenAI, pointed at the host you name.
You choose three things:
- the upstream origin;
- the path prefix that the host serves its API under (
/v1for most; the gateway forwards to it as-is); - the mount: the path this gateway serves the host on, which defaults to the prefix.
A call to <mount>/chat/completions reaches the host at <path_prefix>/chat/completions. Your agent's OpenAI SDK uses the gateway plus the mount as its base URL.
providers:
openai-compatible:
upstream_url: "https://api.mistral.ai"
path_prefix: "/v1" # where the host serves its API (default)
mount: "/mistral/v1" # where this gateway serves it (default: path_prefix)
models: # optional
- "mistral-large-latest"
- "codestral-latest"
| Host | upstream_url | path_prefix | mount (suggested) | Agent base URL |
|---|---|---|---|---|
| Mistral | https://api.mistral.ai | /v1 | /mistral/v1 | https://<gateway-host>:8443/mistral/v1 |
| Groq | https://api.groq.com | /openai/v1 | /groq/v1 | https://<gateway-host>:8443/groq/v1 |
| Together | https://api.together.xyz | /v1 | /together/v1 | https://<gateway-host>:8443/together/v1 |
| OpenRouter | https://openrouter.ai | /api/v1 | /openrouter/v1 | https://<gateway-host>:8443/openrouter/v1 |
| vLLM (self-hosted) | https://vllm.<namespace>.svc.cluster.local | /v1 | /vllm/v1 | https://<gateway-host>:8443/vllm/v1 |
Only one provider can own a path. OpenAI is configured on /v1 by default, and Azure's v1 surface answers on /openai/v1 when enabled. So a compatible host that serves either layout needs a mount of its own. On a collision, the gateway refuses to start rather than guess which upstream a prompt belongs to. Leave mount unset to serve the host on its own prefix, when nothing else owns it.
One gateway serves one compatible host; a second host is a second gateway deployment. Agents keep their own key for the host. The gateway forwards the Authorization header as-is (or none, for a host that needs none), and only changes where the traffic flows.
Models are governed by the name the request uses. A call is authored as llm:send:openai-compatible:<model>, for example llm:send:openai-compatible:mistral-large-latest. The usage record, the LLM BOM and the dashboard carry openai-compatible:<model> exactly as the request spelled it.
A body that names no model is refused (403), and the refusal is recorded, so you can see which SDK call to fix. With a models list, a call that names a model outside it is refused the same way, and the model is recorded so that the list can grow.
Model ids that contain a / (meta-llama/Llama-3.3-70B-Instruct-Turbo, anthropic/claude-3.5-sonnet) need a rule that crosses it. A glob * does not, so write llm:send:openai-compatible:*/* or a regex: rule.
Cost is counted, not estimated. The price catalog carries no entries for these hosts, because list prices differ per host and per plan. So their calls are metered (tokens, budgets and rate limits all apply) and counted in uncosted_calls on the usage summary, rather than priced. Token budgets are exact; a USD budget does not see these calls.
A self-hosted vLLM must be reachable over TLS. Terminate TLS in front of vLLM (an ingress, a mesh sidecar) rather than pointing the upstream at plaintext: the gateway refuses an http:// upstream that is not a loopback test stub.
This is not interception: the agent connects to the gateway by name and verifies the gateway's certificate against the gateway's CA, like any other HTTPS endpoint.
Agents hold no provider keys
By default, an agent authenticates to its provider with its own API key, and the gateway forwards that key as-is (auth: passthrough). A key that an agent holds is a key that can leak, through a prompt, a tool result, a log line or a compromised container.
Brokered keys remove the key from the agent altogether. Under auth: brokered, on each call, the gateway:
- resolves the provider key from the MITRITY credential broker, as a credential definition granted to the agent's mission, exactly as the MCP gateway resolves
${credential:<id>}in tool arguments; - injects the key into the provider's credential header;
- strips every credential header that the agent presented.
The agent configures its SDK with any placeholder key, or none. The provider only ever sees the brokered one.
providers:
openai:
auth: brokered
brokered_key:
value: "Bearer ${credential:openai-prod}" # header defaults to Authorization
anthropic:
auth: brokered
brokered_key:
value: "${credential:anthropic-prod}" # header defaults to x-api-key
credentials:
injection_enabled: true
openai-prod and anthropic-prod are credential definitions in the dashboard, granted to the mission that the calling agent runs under. Rotate the value there, and the gateway switches to the new one at its next heartbeat, within 30 seconds. Remove the grant, and the agent's calls are denied within the same 30 seconds. The agent never had the key, so there is nothing to chase.
The header defaults to what each provider reads (x-api-key for Anthropic, api-key for Azure OpenAI, Authorization elsewhere), and you can set it explicitly. The value template may carry several placeholders. A literal key in the template is refused, because a key in configuration is what brokering exists to avoid.
What the gateway guarantees under brokered mode:
- The key is resolved only for an allowed call. Resolution happens after the policy verdict and after DLP redaction, so a denied call never touches the broker.
- Resolution failure denies. A credential that the broker does not resolve for the calling agent (not granted, revoked, broker unreachable) is a 403 in the provider's error envelope. Nothing is forwarded, neither without a credential nor with the raw placeholder. The refusal is recorded on the audit trail with the credential id (
credential_unresolved), never a value. - The value goes to the provider and nowhere else. It appears in no log line, no audit event and no response.
- Every usage record says which mode the call ran under.
auth_modeispassthroughorbrokeredon every LLM usage record, so you can tell which agents still hold their own keys.
Brokering is per provider. Leave a provider on passthrough, and its agents keep forwarding their own keys, next to a brokered provider on the same gateway. Brokered mode needs credentials.injection_enabled: true on the gateway, which wires the broker. With a brokered provider and no broker, the gateway refuses to start.
Identity: how the gateway knows who is calling
There are two identity paths, and the gateway fails closed when neither yields an identity:
- SPIFFE via mTLS (primary). When the agents run in an Istio mesh, the gateway requires and verifies a client certificate on the TLS handshake. The verified certificate's
spiffe://identity (namespace + ServiceAccount) resolves the calling agent, against the same control-plane binding that the Mesh Authorizer uses. - MITRITY API key (fallback, for non-mesh agents). The agent presents its MITRITY-issued key in a configured header, and the value resolves against the same control-plane index. The header is never forwarded to the provider and never logged. Agents on this path cannot present a client certificate, so client-certificate verification must be relaxed for them at install time.
A valid identity that maps to no governed agent is denied by default. An allow mode exists strictly as a migration escape hatch. An absent identity is always denied, regardless of configuration.
Mesh mode: one hop governs the whole fleet
The gateway runs in one of two identity modes, set by llmGateway.identityMode:
mesh(default): the centralized, N-to-1 egress hop. A single gateway Service governs every agent in the environment. It resolves each caller per request (SPIFFE mTLS, or the API-key fallback) against a(namespace, ServiceAccount) → agentindex. Then it enforces that agent's policy: its model allowlist, token caps, budgets, rate limits and DLP. This is the mode for the shared in-cluster LLM-egress hop. It is how mesh customers get the body-level LLM governance that the Mesh Authorizer cannot provide, because the Mesh Authorizer is L3/L4 and never sees the prompt.colocated: the 1-to-1 sidecar. The gateway serves a single configured agent, and trusts a same-pod loopback call as that agent. It is used when a gateway is deployed next to exactly one workload.
Mesh mode needs a mesh-edge credential. To govern the fleet, the gateway must hold every served agent's policy, and it gets those policies from its own heartbeat. So in mesh mode, llmGateway.agentId must be your environment's mesh-edge credential: an agent that you create with is_mesh_edge: true.
A mesh-edge agent's heartbeat returns the mission profiles of every mesh-bound agent in its environment (fleet provisioning). That is what populates the resolution index. If you point the gateway at a plain agent, it receives only that one profile, so every other caller resolves to nothing and is denied.
Delivery is scoped to the mesh edge's own environment, for least privilege. The flag can be set only through the agent API, never by an agent that registers itself. This is the same fleet-provisioning mechanism that the Mesh Authorizer uses to serve many agents from one deployment.
Once the gateway is installed, confirm that the fleet is flowing: the gateway logs heartbeat synced mesh fleet with the mesh-bound profile count. A mesh gateway that never logs it is almost always pointed at a non-mesh-edge credential.
Response DLP and streaming
Response-phase DLP scans the text that the model generates, before it reaches the agent. It complements the request-phase prompt scan. It is on by default.
-
Non-streaming responses are buffered whole and scanned, then blocked, redacted or delivered. This is fail-closed: a model output that the gateway cannot fully read, parse or (after a redact match) rewrite is never delivered.
-
Streaming (SSE) responses use a holdback-window scan. The gateway withholds the last few kilobytes of generated text from the agent, so that a secret straddling chunk boundaries can still be matched, redacted or blocked before delivery. Stream framing (roles, tool calls, usage frames, pings) passes through untouched.
-
Thinking is inspected, never rewritten. A model's reasoning is signed by the provider, or replayed by the agent on the next turn as if it were. Examples are Anthropic thinking blocks, Vertex thought parts and the
reasoning_contentthat an OpenAI-compatible host returns. So the gateway cannot scrub reasoning the way it scrubs text. It scans it instead, on every leg:- in a prompt that replays a thinking block;
- in a buffered response;
- in a stream, where thinking frames are held back whole under the same window, and released byte for byte once the thinking behind them has been scanned.
A DLP pattern that would redact thinking denies the call or terminates the stream instead, and nothing partial is delivered. A block pattern blocks and a flag pattern records, as usual. Anthropic's
redacted_thinkingblocks are ciphertext and pass through uninspected.
Streaming holdback: the bounded-window caveat
The holdback window is bounded (default 2 KiB, configurable from 256 bytes to 1 MiB), so that the gateway never buffers an entire stream. As a consequence, a single DLP match longer than the window that straddles the window edge can escape the streaming scan. Size the window above your longest expected pattern match; a typical credential or PII token is far under 2 KiB.
Non-streaming responses are unaffected, because they are always scanned whole. Raising the window trades a little time-to-first-token latency for a wider straddle guarantee.
Privacy guarantee
Prompts and model responses never leave your cluster, except to the LLM provider that you explicitly configured as the upstream. The gateway evaluates everything locally, with the same in-process decision engine as the other MITRITY binaries. Only two things reach the MITRITY control plane:
- verdict metadata: decision, policy and pattern IDs, provider, model, redaction counts;
- token counts.
Prompt and response content is never included in events, logs or the dashboard.
The agent's provider API key is forwarded only to the provider, or, under brokered mode, replaced by a key that the agent never held. The MITRITY identity header (API-key fallback) is never forwarded upstream and never logged.
Fail-closed behavior
The gateway is inline on the agent-to-provider path, and it fails closed:
- A request with no resolvable identity, or an identity bound to no governed agent, is denied.
- A mission-scope, DLP-block or threat-intel verdict on the prompt denies the call. The error is provider-shaped, in that provider's own JSON envelope, so agent SDKs surface it cleanly.
- A policy verdict that requires human approval is denied at the wire, because a synchronous proxy has no pending state. Approve the request in the dashboard approval queue, and the agent retries.
- Gateway down = governed traffic down. Agents whose
base_urlpoints at an unavailable gateway cannot reach the provider at all. That is the fail-closed contract. Run at least two replicas (the default), and keep them close to the agents.
LLM usage, cost & BOM
Each governed call emits a usage record, keyed to the calling agent. The record holds the provider, the model, the input/output/total token counts, the decision (allowed, blocked, or redacted) and the redaction count. These records power the LLM Gateway page, under Monitoring in the dashboard sidebar:
- Per-call usage records: every LLM Gateway-governed call, filterable by agent, provider, model, decision and time range. A blocked call is recorded with zero tokens, because it was never forwarded.
- Token spend trends and estimated cost: usage aggregated by model, agent or day.
estimated_cost_usdis computed from a static price catalog keyed by (provider, model) that ships with platform releases. Prices are estimates, never billing data. A group's estimate is null when no call in it matched a catalog entry, anduncosted_callscounts the calls excluded for that reason. - LLM Bill of Materials (BOM): a per-agent model inventory, with one entry per distinct (agent, provider, model) ever observed through the gateway. Each entry has first-seen/last-seen timestamps and lifetime call and token totals. The BOM answers "which agents talk to which models", the same way the AI Bill of Materials answers it for tools.
The same usage numbers drive the per-policy cost budgets, the burn-down panel and the spend alerts in Guardrails. What the gateway measures is also what it enforces.
To check that those guardrails hold under pressure, and not only in configuration, point a Red Teaming campaign at the route. It sends adversarial attempts through this gateway, on a budget that you set. It records the attempts that the guardrails let through as findings, each with a remediation.
No prompt or response content is ever included. Usage records are metadata only, consistent with the privacy guarantee above. The same data is available programmatically through the /llm-usage API endpoints.
Deployment
The Mitrity LLM Gateway ships as a Helm chart, published as a cosign-signed OCI artifact with every MITRITY edge release:
helm install mitrity-llm-gateway \
oci://ghcr.io/mitrity-io/charts/mitrity-llm-gateway --version <X.Y.Z> \
--namespace mitrity-system --create-namespace \
--set llmGateway.apiKey=$MITRITY_API_KEY
Key facts:
- Available from MITRITY edge release v0.13.0. The chart version tracks the release tag (
vX.Y.Z→ chart versionX.Y.Z). - Off by default. The gateway capability is flag-gated in the edge binary, and installing the chart is the opt-in.
- Mesh mode wants a mesh-edge credential. In the default
identityMode: mesh, setllmGateway.agentIdto an agent created withis_mesh_edge: true, so that its heartbeat provisions the fleet (see Mesh mode above). In mesh mode, startup fails fast ifagentIdis empty. - cert-manager provisions the gateway's own server certificate on the default TLS path; you can bring your own TLS Secret instead. Every hostname that agents will use in
base_urlmust be on the certificate. - Istio is optional. The gateway does not require a mesh. When agents run in an Istio mesh, issue the gateway's certificate from the mesh trust domain, so that in-mesh agents present SPIFFE client certificates automatically. Agents need no changes beyond the base URL.
- Secure by default. The gateway refuses to open a plaintext listener on a non-loopback address. An egress gate that sees prompt bodies never runs unencrypted on the network.
The chart README packaged with the release covers the full install, provider configuration, certificate setup, a troubleshooting table and signature/SBOM verification.
Related Documentation
- Integration Modes: Mitrity Gateway and Mitrity MCP Sidecar
- Mesh Enforcement (Istio): the in-mesh governance surface and its capability table
- Per-Agent Identity in the Mesh: the namespace + ServiceAccount binding that the SPIFFE identity path uses
- API Overview: the
/llm-usageendpoints for usage records, summaries and the BOM - Red Teaming: governed adversarial campaigns that test these guardrails on a live route
- Microsoft 365 Copilot and Copilot Studio: the connector that discovers, inventories and posture-assesses the Microsoft agents that never leave Microsoft