Skip to content

Guide10 min read

Guardrails developers won't route around: a checklist for the governed path

When an AI control is slower or more confusing than calling the model directly, developers switch to a personal key and the control protects nothing. A checklist for making the governed path the easiest one, with a test for each item.

Sam O.Head of Security Research

Sam O. leads security research at Intertrace and designs the enforcement layer that decides what an AI agent may do before it acts. The work centers on agent authorization, adversarial testing and approvals people actually read, and Sam publishes what the team's own tests find, including what they miss. Sam also writes essays on how AI changes work and institutions.

August 1, 2026 · 10 min read

Developer ExperienceAI GatewayShadow AIPlatform EngineeringGuardrails
Contents
  1. 011. Interview the teams that left before changing anything
  2. 022. Make a key take minutes, and keep provider credentials central
  3. 033. Keep the provider's API and change only the base URL
  4. 044. Spend inspection where one call can do damage
  5. 055. Make every denial say which rule, why, and what next
  6. 066. Put every new rule through monitor mode and a replay
  7. 077. Let teams grant themselves exceptions that expire
  8. 088. Measure bypass from outside the control
  9. 09Items 2 to 7 in one gateway
  10. 109. Close the side doors last

A quarter after the AI gateway launched, the provider invoice is larger than the gateway's own usage numbers can explain. Someone traces the difference to personal keys on corporate cards, a team calling the provider directly from a batch job, and an evaluation pipeline that was moved off the gateway because, in the team lead's words, it kept blocking things and nobody could say why.

The usual response is to treat that as a compliance failure and enforce harder. Treat it as a product failure instead. Every developer who routed around the control paid a cost its designers did not see. Fix the cost and most of the bypass disappears; enforcement then handles a remainder that is much smaller and much easier to justify. Enforce first, and the same workloads move onto laptops and personal accounts, where nothing can see them. The checklist below runs in the order to do the work, and each item comes with a way to test it.

1. Interview the teams that left before changing anything

Bypass has a small number of causes, and each has a specific design fix. Interview three teams that left the governed path and sort what they tell you into this table.

Common frictions in AI controls, what developers do instead, and the design fix
FrictionWhat the developer experiencesWhat they do insteadDesign fix
Setup costA ticket and several days of waiting for an API keyCreate a personal key in the provider consoleSelf-serve keys in minutes, scoped to a project and an environment
A different APIAn internal SDK wrapper with its own request shape that lags the provider's featuresCall the provider SDK directlyKeep the provider's API and change only the base URL
Latency everywhereEvery call, including trivial ones, waits on the heaviest checkTurn the control off in development, then forget to turn it back onCheap checks for low-consequence calls; spend time where consequence is
Opaque denialsA bare 403 that says the request was blockedRetry, rephrase the prompt, or switch pathsA denial with the rule, the reason, a decision ID and a next step
No exception pathA feature blocked for weeks waiting on a reviewShip a shadow deployment on a separate keySelf-serve, time-limited exceptions with a named owner
Feature gapsStreaming, tool calls or a new model work directly but not through the controlUse the direct path for the features that matterTreat feature parity as a requirement and publish the gaps

Two rows do most of the damage. Setup cost decides which path a new project starts on, and projects rarely move later. Opaque denials decide whether a team stays: a developer who hits three unexplained blocks in a week stops trusting the path, even if each block was correct. Test: every reason you heard maps to a row. A reason that does not is a friction you have not designed for yet.

2. Make a key take minutes, and keep provider credentials central

Issue keys self-serve per project or per agent, tagged with an environment such as development or production, so policy can treat a test run differently from production traffic. Hold provider credentials centrally and never hand them to developers. If the only working provider key lives behind the control, a personal key is not faster; it is a second account to set up and expense. Test: measure minutes from a new project's key request to its first successful call. It should be short enough that nobody looks for a shortcut.

3. Keep the provider's API and change only the base URL

Make the governed path a configuration change, not a code change. Most model SDKs already let you point the client at a different endpoint. The OpenAI Python library, for example, accepts a base_url argument and also reads the OPENAI_BASE_URL environment variable [3]. If your control speaks the same API, a team adopts it by setting one variable and swapping one key, and every feature the provider ships keeps working unless you deliberately block it.

Send tool traffic through the same front door. If agents reach MCP servers and internal tools through the endpoint they use for model calls, there is one thing to configure, one key to rotate and one place where policy applies. Put the governed base URL into every project template, internal starter kit and CI image, so a new service is on the governed path before anyone makes a choice. Test: streaming, tool calls and the newest model work through the control exactly as they do directly. Treat feature parity as a requirement and publish any gap.

4. Spend inspection where one call can do damage

A chat completion that summarizes an internal document and a tool call that deletes a production table are different risks. A control that charges both the same latency will be resented on the first and under-powered on the second. Sort calls by consequence, meaning the worst realistic outcome of one call judged by reversibility and reach, and spend inspection where it changes outcomes.

Spend checks where consequence isFig. 1
Agent callmodel or toolRead the calltool name + argumentsREADS · REVERSIBLE CHANGESCheap checksreal secrets · card numbersRunsno waitIRREVERSIBLE · OUTBOUND · MONEYFull inspectionarguments · policy · DLPAsks a personBlockedstructural checks run on every call · the expensive work runs only where one call can do damage

Reads and reversible changes take a short path with cheap checks and run; irreversible, outbound, financial and administrative calls take the full path and may ask a person or be blocked. Most developer traffic never waits on the expensive path.

Environment matters as much as action. In development, an agent deleting a row in a disposable test database should not wait for a person; in production, the same call should. Outbound messages, payments and administrative changes are the exception, because they reach the outside world or change who can do what. They should ask everywhere, including development, where a test message to a real customer is still a real message.

The fast path is not a weaker path. Structural checks, such as detecting real credentials or card numbers in a prompt, are cheap and run on every call; what the fast path skips is expensive work that only matters when a call can do damage. Test: run the same delete in development and production and confirm only production asks, then send a test message from development and confirm it asks too.

5. Make every denial say which rule, why, and what next

Most developers meet your control for the first time when it says no, so the denial is the control's user interface. A bare 403 that says request blocked is the worst one available. It does not say what is wrong, whether it is the developer's fault, or what to do next, so they rephrase, retry, and eventually find another path.

HTTP already has a standard for this. RFC 9457 defines problem details: a machine-readable error body with a type URI that identifies the problem, a short human-readable title, a detail field specific to this occurrence, and an instance URI for the occurrence itself, and it allows problem types to add their own extension members [1]. Use it, and add the fields developers need to act.

A denial developers can act onFig. 2
Bare denialHTTP 403Request blocked.WHICH RULE?WILL A RETRY HELP?WHO DO I ASK?Structured denialILLUSTRATIVE VALUES403 · application/problem+jsontype/problems/data-ruletitleCard number in requestdetailcard-like value in messages[2]ruleorg.data.card-numberslayerorganizationretryno · remove the value firstdecisiondec_7f3a91nextrequest exception · rule docsa denial is the control's user interface · it should tell the developer what happened and what to do next

The bare denial on the left gives the developer nothing to do but retry or leave. The structured denial on the right names the rule and the reason, says whether a retry helps, and links to the exception request.

  • Name the rule and the layer that decided, for example an organization-wide data rule versus a rule specific to this agent, so the developer knows whom to talk to.
  • Say what was found and where, without echoing the sensitive value: a value that looks like a card number in the third message, not the number itself.
  • Say whether a retry can succeed. Policy denials will not change on retry; rate limits will. RFC 6585 defines 429 Too Many Requests for rate limiting and allows a Retry-After header saying how long to wait [2]. Keep the two distinct so client retry logic does the right thing.
  • Return a decision ID that support and security can look up, so a developer can report a false positive in one message.
  • Link to the next step: the documentation for the rule, the exception request, or the approval that is now pending.

Test: show three developers a real denial and ask what they would do next. If the answer is retry or rephrase, the denial has failed.

6. Put every new rule through monitor mode and a replay

Before a new rule blocks anything, run it in monitor mode and replay it against recent traffic. Count which teams it would have blocked and read a sample. A rule that would have denied a hundred legitimate calls a day is not ready, however sound its threat model. Test: no rule reaches enforcement without a replay result attached to its change.

7. Let teams grant themselves exceptions that expire

Some denials will be correct in general and wrong for one team this week. If the only remedy is a security review with a two-week queue, the team will find another path. Make exceptions self-serve within limits:

  • An exception is named, scoped to one agent or project, and carries a written reason and the requester's name.
  • It has a default expiry measured in days and a hard maximum, so it cannot quietly become permanent.
  • Low-consequence exceptions take effect immediately and are reviewed afterward; exceptions that touch outbound, financial or administrative actions need an approver first.
  • The agent's owner and the security channel are notified automatically, and every decision made under the exception records that the exception allowed it.
  • An exception renewed repeatedly is evidence that the rule is wrong, not that the team is careless. Review renewals monthly.

8. Measure bypass from outside the control

The headline metric is governed share: the fraction of AI usage that goes through the control. The difficulty is the denominator, because bypassed traffic is by definition the traffic you do not see. Build it from several sources. AI provider organizations list keys and usage per key. Expense systems show subscriptions bought on cards. Egress logs and DNS show which hosts call provider API domains. Identity providers show OAuth grants to AI applications. Repository scanning finds committed keys; GitHub secret scanning, for example, lists both OpenAI and Anthropic API keys among its supported patterns [4].

Metrics that show whether developers prefer the governed path
MetricHow to compute itHealthy direction
Governed share of spendSpend through the control divided by total provider spend across all organizations and expensed accountsRising toward nearly all
Keys outside the controlProvider keys not issued through the control, from provider org listings and repository scansFalling, with each one assigned an owner
Time to first governed callMinutes from a new project's key request to its first successful callShort enough that nobody looks for a shortcut
Denials overturnedShare of denials later reversed as false positives or granted an exceptionLow and falling; a spike means a bad rule
Time from denial to resolutionMinutes from a denial to a successful call or a granted exceptionFalling
Exception renewalsExceptions renewed more than once for the same ruleEach renewal reviewed as a rule defect

When you find a bypass, contact the team before you revoke anything. Ask what they were trying to do and what stopped them on the governed path. Most answers map to a row in the friction table, and fixing that row helps every team rather than one.

Items 2 to 7 in one gateway

With Intertrace, teams change their base URL and use a runtime key, and the same front door covers model calls, MCP traffic and agent-to-agent messages. The gateway assigns each call a consequence tier from the tool name and its arguments, so reads and reversible changes run while outbound messages, payments and administrative changes ask a person. Each decision records its rule and layer. Exceptions are named, scoped to one agent and expire after seven days by default and ninety at most, and a proposed rule can be replayed against the last thirty days of real decisions before it blocks anything. The interviews and the side doors stay yours.

9. Close the side doors last

Hollin, a fictional online retailer with forty product teams, started where many companies do (numbers illustrative): keys took four days, a custom SDK lacked streaming, denials were generic, twenty-three provider keys had never been issued through the gateway, and a little over half of AI spend went through it. The platform team fixed the friction first, then gave every owner of an outside key thirty days' notice and a migration offer before revoking; by the end of the quarter governed share was above ninety percent, time to first call was under ten minutes, and the most common exception had exposed a rule that flagged internal order IDs as card numbers.

That is the right order. Once the governed path is the easier one, restrict egress to provider API domains from production networks, revoke provider keys that were not issued through the control, turn on push protection for provider keys in your repositories, and decline expensed AI subscriptions that duplicate a governed service. Announce each step with a date and a migration offer. Done last, each one removes an inferior option instead of the only one that worked.

Sources

Reporting, research and specifications this piece draws on. Links go to third-party sources; analysis and recommendations are Intertrace’s own.

  1. 01RFC 9457: Problem Details for HTTP APIs · IETF
  2. 02RFC 6585: Additional HTTP Status Codes (429 Too Many Requests) · IETF
  3. 03OpenAI Python API library: configuring the HTTP client · OpenAI
  4. 04Supported secret scanning patterns · GitHub Docs

About the author

Sam O.

Head of Security Research

Sam O. leads security research at Intertrace and designs the enforcement layer that decides what an AI agent may do before it acts. The work centers on agent authorization, adversarial testing and approvals people actually read, and Sam publishes what the team's own tests find, including what they miss. Sam also writes essays on how AI changes work and institutions.

All 17 pieces by Sam O.

Dated August 1, 2026 · added to intertrace.ai October 19, 2026

+ Runtime enforcement

See these controls on your own agent traffic.

Intertrace sits inline between your agents and the models, tools and MCP servers they call, and decides every call before it runs.

Keep reading

All insights →