The authorization field has settled one half of its problem. The AuthZEN Authorization API reached Final Specification in January 2026 and standardizes how an enforcement point asks a decision point a question, so any compliant one can call any compliant engine. What no specification settles is where those enforcement points should sit, or how the decision point gets the facts it needs. CoSAI’s agentic identity guidance gives the sharpest answer available for AI systems, which is that enforcement has to happen at every hop and at the last mile, at the tools, the APIs and the data systems, not only at the initial gateway.
Outbound model traffic is one of those hops, and usually the one nobody owns. It does not come out of one application. It comes out of the product that shipped a summarization feature last quarter, the coding assistant in every engineer’s editor, and the agent triaging support tickets. What actually goes out is rarely written down anywhere a security team can read, and spend accrues on an invoice that arrives weeks later, itemized by model rather than by person.
The only real lever most organizations hold over this is the API key, and a key is a binary. Revoke it and the feature stops working. Leave it in place and it says nothing about what the caller may ask for, how much a request may cost, or whether the file an assistant is about to read should go to a third party. What the problem has going for it is that all of it converges. The request has to leave the network through whatever holds the credential, so if that component can pause and ask a question before forwarding, the answer can come from policy instead of key distribution.
The egress point is already in the path
Aperture by Tailscale already occupies that position. It sits in front of outbound model calls from a tailnet, holds provider credentials centrally rather than distributing them, and authenticates callers by their Tailscale identity, so a request arrives attributed to a person and a device rather than to a shared secret.
The architecturally interesting part is what it does before forwarding. Aperture fires hooks at defined points in the request lifecycle, and one of them is synchronous. The pre_request hook runs before the provider is contacted, and Aperture waits for the response before the call proceeds. The other events fire after completion and their response bodies are ignored, which makes them useful for observability and useless for enforcement.
The hook itself is an ordinary HTTP endpoint returning allow, block or modify. That is the whole contract, and it is why the pattern generalizes past one vendor. Any component with an outbound choke point and a synchronous callback can delegate the same way. It is also the shape reference architectures converge on, where the thing guarding the door holds no logic of its own. The gateway does not need to know what a budget is, only where to ask.

What the hook payload lets a policy reason about
A synchronous callback is only as useful as the payload it carries. Aperture always sends request metadata, covering the caller’s login name, the tailnet, the node, the user agent, the model and provider, and request identifiers. Everything else is named in the hook configuration. Tool calls, the user message, an estimated cost, the caller’s grants and their current quota state are all opt in.
That list sets the ceiling on what any policy downstream can reason about, and decides what leaves the gateway. Sending the user message to a decision endpoint puts the prompt in a second place, so that endpoint inherits the handling requirements of what it inspects.
On the receiving side, Cerbos Synapse ships a bundled extension for this hook, so the integration is configuration rather than an adapter binary. Its mapping is fixed and worth knowing. The login name becomes the principal id, and the tailnet name, node id and user agent become principal attributes. The whole hook body becomes the resource attributes, so every field the gateway sends is readable inside a resource policy. The action is one fixed verb, because at the egress point there is exactly one thing being asked.
That translation step is itself becoming standard work. A draft AuthZEN framework specification from February 2026 defines a protocol neutral way to map any information model into the standard subject, action, resource and context request, using CEL expressions by default. The bundled extension is the same shape, hand fitted to one hook format. That draft is co-authored at Cerbos and CrowdStrike, worth stating since this article cites it.

Cost limits, credential shapes and tool arguments in one rule set
The policy set used to exercise this integration runs five deny rules and one terminal allow. The spread is the point. One blocks any tool call whose command, file path or path argument references a sensitive location, checking each argument name different agents use for the same thing. A second blocks destructive shell idioms. A third refuses any request whose estimated cost exceeds a per request ceiling. A fourth inspects the user message for the shapes credentials take when pasted into a chat, a bearer token inside a curl command or a block of .env contents. A fifth gates destructive MCP tool calls to a named set of tailnets.
Between them those reach three dimensions of one request. Cost is a property of the call. Credential shapes are a property of the prompt. Destructive verbs gated by tailnet are a property of the caller combined with the invocation. Gateway native rules generally match a model name or a route, but matching the content of a shell command an agent is about to run, against the attributes of the person whose session produced it, needs both halves in one evaluation.
Roles arrive from this hook as a single fixed value, so role based rules have nothing to distinguish. Conditions are written against the principal id or the tailnet instead, and richer context has to come from a data source that enriches the request. That is the shape of any externalized authorization integration where the calling protocol carries less identity than the policy wants, and it is precisely the part no specification covers.

A denial is not an error, and the difference is specified
AuthZEN is strict about one distinction this integration depends on. A policy denial is an HTTP 200 carrying a decision of false. A 4xx means the enforcement point failed to reach or authenticate to the decision point. A malformed request is never a denial, and a denial is never a transport failure.
The Aperture path preserves that separation. The extension always answers with HTTP 200 and a JSON body carrying both the decision and the status the gateway should render to the caller. Those fields come from policy outputs, so the rule that fired chooses what the caller sees, and a broken decision endpoint stays distinguishable from a decision.
This is not cosmetic. The default status for a block is 403, and a coding agent receiving that from what it believes is a model provider will conclude its credentials have expired and prompt the user to log in. Nothing changes on re-authentication, and the control has produced confusion rather than compliance. Overriding to 400 for a content rule and 429 for a budget rule points the client’s error handling at the actual cause. The message carries the rest. A developer told a destructive MCP call is restricted to approved tailnets knows what to do next. One told only that the request was forbidden pastes the same prompt somewhere unmonitored.
The standards work is already a step past this. The AuthZEN Access Request and Approval Profile, ARAP, a Standards Track draft from July 2026, defines a requestable denial, where the denial carries a bound path to ask for approval and the approval becomes an input to a fresh evaluation rather than a standing grant. An explanatory message is the informal version of that, worth building now because a denial nobody understands is a control people route around.
What changes when model usage rules become policy
Model usage rules stop being key distribution and become a policy file reviewed, versioned and tested like any other. They can then reference things a key cannot represent. Cost, tailnet, tool arguments and the caller’s quota state are readable in one condition, which is the property that makes per tool and per argument authorization work one layer in. The rules covering coding agents and the ones covering outbound traffic share a vocabulary and a bundle.
Every outbound call then produces a decision record with a principal, a matched rule and the attributes that fired it. That is the same audit stream an Envoy edge integration writes into, so AI usage becomes more rows in a log the security team already reads, not a separate reporting problem.

What an egress control does not cover
CoSAI’s guidance argues this article’s limits better than the article can. Enforcement at the initial gateway alone is what produces confused deputy and privilege escalation failures as tokens propagate down an agent chain. An egress point is one hop, and it is not the last mile.
The coverage of this control is therefore exactly the coverage of the network. A workload with direct egress to a provider is not covered by any of it, which is an outcome of network design rather than the authorization layer, and the kill switch question is the right way to test whether you have it. The same check still has to exist at the tool, the API and the data system, which is why an agent identity reaching those systems needs its own decision rather than one inherited from the boundary.
Prompt inspection deserves the same honesty. Matching credential shapes in a user message is pattern matching against conditions somebody wrote. It catches the common accidents, pasted curl commands and copied configuration files, and comprehends nothing. Encode the same secret, split it across turns, or describe it in prose and the pattern does not fire. Tailscale’s own guardrails documentation makes the equivalent point about detection accuracy. The obvious fix, asking a model to judge the content, is the one thing OWASP AISVS rules out, since access decisions must be enforced by application logic or a policy engine, never by the model itself. What is left is a deterministic control with known evasion. Treat it as that, not as data loss prevention, and do not let it be recorded as the latter in a compliance narrative.
Two configuration choices decide the failure mode, whether an unreachable or slow decision endpoint fails open or closed, and how long the gateway waits. CoSAI is unambiguous for high capability agents, requiring enforcement gateways to be fail closed with a defined safe degradation path, meaning human review or a halt rather than quiet continuation. Fail closed without that path only moves the outage. Worth checking alongside it, a missing policy allows traffic through here, so an empty bundle is permissive.
None of that makes the egress point the wrong place to check. It sees everything that does traverse it, while there is still something to be done. Once the call has been forwarded, the prompt is in somebody else’s logs, the spend has happened, and the remaining option is to write it down. Moving the decision one step earlier turns model usage into something with guardrails that can be argued with and changed. Then you do the same at the next hop.
Try Cerbos to see how this works in practice, or book a call to talk through your architecture with the team.
Go deeper:
- Zero Trust for AI, securing MCP servers (eBook) for the deployment patterns and policy shapes behind governed AI access
- Fine-grained authorization for AI gateways for how the same decision layer applies to inbound gateway traffic
FAQ
Tagged in






