Runtime security for agents: policies and gateways
Rules written in a prompt can be talked around. Rules enforced by a gateway between the agent and its tools cannot. Here is how to build that layer.
In 60 seconds
Runtime security for agents: policies and gateways
Rules written in a prompt can be talked around. Rules enforced by a gateway between the agent and its tools cannot. Here is how to build that layer.
What a gateway can do
| Function | Example |
|---|---|
| Allow-list tools and arguments | This agent may call search_tickets and add_comment; add_comment only on tickets assigned to its team |
| Attach identity | Adds the verified agent and user identity to each call, so tools do not trust the model's claims |
| Data loss prevention (DLP) on outputs | Blocks or masks card numbers, Aadhaar-like numbers, keys and passwords in replies and outbound calls |
| Rate and budget limits | At most 20 refunds proposed per hour; a cost cap per task |
| Approvals | Pauses high-risk calls and sends them to a human with full details |
| Anomaly detection | Alerts on first-time tools, new destinations, spikes and loops |
| Kill switch | One setting that stops all calls for an agent, a tool or everything |
| Logging | One consistent record of every call, for monitoring and incident response |
Policy as code
from dataclasses import dataclass
@dataclass
class ToolCall:
agent: str
user: str
tool: str
args: dict
POLICY = {
"support-agent": {
"allowed_tools": {"search_kb", "get_order", "draft_reply"},
"needs_approval": {"issue_refund"},
},
}
def decide(call: ToolCall) -> str:
rules = POLICY.get(call.agent)
if rules is None:
return "deny: unknown agent" # default deny
if call.tool in rules["needs_approval"]:
return "hold: human approval"
if call.tool not in rules["allowed_tools"]:
return "deny: tool not allowed"
if call.tool == "get_order" and call.args.get("customer_id") != call.user:
return "deny: not this user's order" # identity from runtime, not model
return "allow"- Default deny: unknown agents and unknown tools are refused.
- Identity from the runtime:
call.useris set by the platform, never taken from the model's text. - Arguments checked, not just tool names: an allowed tool with the wrong target is still denied.
Output checks and DLP
- "Never call the refund tool without approval" in the system prompt
- "Do not reveal card numbers"
- "Only look up the current user's orders"
- Refund calls are held until a human approves
- Outbound text is scanned and card-like numbers are masked
- The order tool rejects any customer ID not matching the verified user
The kill switch
Watch and read more
Lab
A tested policy function that blocks tool calls outside an allow-list and holds risky ones.
The problem
On your own machine, save the starter code and run it. The tests at the bottom describe what the policy must do; some fail now.
- Make
decidedeny unknown agents and tools not on the allow-list. - Hold
issue_refundfor approval, and deny it outright if the amount is above the agent's hard limit. - Deny
send_emailto any domain not in the allowed list. - Add a rate limit: after 5 allowed calls to the same tool by the same agent in one run, deny.
- Add two tests of your own.
Practice lab
Edit the code and run it. Python runs in your browser — nothing is sent to our server.
No output. Add print(...) to see values.
You are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1Your email check splits on "@". What tricky inputs should you test?Reveal
Addresses with more than one @, uppercase domains, extra spaces, subdomains (a@evil.example.com vs a@example.com), lists of recipients in one string, and CC or BCC fields. Normalise (lowercase, trim), take the part after the last @, compare exact domains, and check every recipient field, not only "to".
Q2The policy holds refunds for approval. What should the approval request show so the human can decide well?Reveal
The customer, order, amount, the reason the agent gave, the source that led to it (for example the ticket text), whether this customer had recent refunds, and what is unusual. Also say whether it can be reversed. A request that says only "approve refund?" invites a blind click.
Questions people ask
Does a gateway add delay?
A little, usually small compared with the model's own response time. Simple allow-list and argument checks are fast. Heavier checks (scanning large outputs) cost more, so apply them where the risk is.
Should the gateway use an AI model to judge requests?
A model-based classifier can help flag unusual requests, but it can be fooled like any model. Keep the hard rules (allow-lists, identity, limits, approvals) in plain code, and use AI checks as an extra signal, not the final gate.
Where does the gateway live?
Wherever all traffic must pass: as a proxy in front of tool servers, in your MCP client or agent platform, or in a network layer. What matters is that agents have no other route to the tools.
How do we test policies?
Write tests like any code: calls that must be allowed, calls that must be denied, calls that must be held. Run them on every policy change. Add a test each time an incident teaches you something new.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.