How do you enforce runtime controls on AI agents accessing enterprise systems?
You enforce at each action, not at the connection. When an agent reads from a CRM, a data warehouse, or an internal API, put a decision point inside the tool handler that runs before the read or write completes. Give that decision point three things to check: a budget keyed on the identity that you are protecting and sitting at the tool call, injection detection on every untrusted input including data that the agent just read, and sensitive-information detection at the boundary.
The reason this is necessary rather than redundant is that identity controls have already passed by that point. They were designed to.
The agent is authorized, and that is the problem
When you connect an agent to an enterprise system, the access review passes. It has a service identity, scoped credentials, and an audit log on the target system. Everything that an IAM review asks for is in place.
Then the agent reads a record, decides its next step from what it read, and acts. The credential was valid at every step. Nothing in the identity layer was violated. The outcome is one that nobody authorized, because you granted authorization to the agent, not to the action.
Scoped credentials answer "can this agent reach this system?" They can't answer "is this particular operation, with these arguments, for this user, allowed right now?" That second question needs information that only exists at the moment of the call.
What does a runtime control check?
| Control | What it catches | Why the identity layer misses it |
|---|---|---|
| Budget per identity, across the loop | A workflow that consumes far more than intended, whether from a non-terminating loop, a retry storm, or an injection | Credentials have no notion of consumption, and per-request limits only count workflow starts |
| Injection detection on untrusted input | Hostile instructions arriving through enterprise data the agent reads and then acts on | A record the agent is permitted to read is still untrusted content once it re-enters model context |
| Local sensitive-information detection | Protected data moving through a tool into a response, a log, or an embedding | Access grants say who can read, not where the data can go afterwards |
Budgets that hold across the loop
The control is a token bucket keyed on the user or session, drawn down by each call in proportion to cost. Calls share a bucket by key, not by correlationId. The following sample keys on session.userId, so every matching call for that user draws from the same bucket, including concurrent runs. A limit applied only at the HTTP entrypoint would see one request and allow all 40 tool calls. If you want a per-run budget instead, key on the run ID and say so.
Enterprise data is untrusted input
Injection checks belong on every untrusted input, including the user's opening prompt and the tool outputs you feed back to the model.
This is the part that teams underestimate. A comment field in a CRM record, a description on a warehouse row, a ticket body, or a filename in a document store are all places where text authored by someone outside your organization ends up inside your agent's context. Enterprise data becomes untrusted input the moment an agent reads it and acts on it, and the fact that it came from your own database doesn't make it safe. The OWASP Top 10 for LLM Applications treats this as indirect prompt injection, and it is the variant most likely to appear in an internal integration.
Enforce where there is no HTTP request
Agent access to enterprise systems runs through tool calls and background jobs. There is no request object, so proxies, WAFs, and HTTP middleware never see it. The control has to live inside the function.
import { launchArcjet, tokenBucket } from "@arcjet/guard";
const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
const budget = tokenBucket({ refillRate: 100, intervalSeconds: 60, maxTokens: 500,});
// Inside the tool handler, before the agent reads anythingconst decision = await arcjet.guard({ label: "tools.read-customer-record", actor: session.userId, correlationId: workflowRunId, rules: [budget({ key: session.userId, requested: 1 })],});
if (decision.conclusion === "DENY" || decision.hasFailedOpen()) { // Stop before the agent reads throw new Error(`Denied: ${decision.reason}`);}Two details are worth getting right. Derive actor from an authenticated server-side identity, never from user-controlled input. A policy can be conditioned on the actor, so an attacker who controls the actor can escape their own policy scope. Set correlationId to your workflow run ID, so that decisions from a single agent run are attributable to that run rather than scattered as unrelated events.
A direct Guard call fails open: an allow can mean evaluation failed, and the return includes error codes. Check hasFailedOpen() on sensitive reads; otherwise a timeout lets the action proceed. Vercel AI SDK and LangChain wrappers fail closed unless you opt into continuing on error. HTTP request checks can fail open when Arcjet's cloud can't be reached; that behavior is configurable.
Arcjet validates label server-side as a slug: lowercase letters, digits, dash, and dot only, starting and ending with a letter or digit. Use one hardcoded label per specific operation rather than building it from a variable, so the label stays greppable and groups cleanly in reporting.
Separation of enforcement and policy
The reason this is operable at enterprise scale is that where enforcement runs and who owns policy are different things.
Developers embed the controls in code, reviewed like any other change, in the same pull request as the tool that they protect. Security teams change Guard remote policies without a redeploy (allow/deny lists, length, membership, prompt injection, and local sensitive-info), roll out in dry run to measure first, and inspect decisions through the Console, CLI, or MCP server.
Token-bucket refill and capacity are not remote: those numbers live in application code. HTTP remote rules on protect() (bots, Shield, filters, fixed-window and sliding-window rate limits) are a separate system, site-wide, and don't support token bucket. Threat modeling, incident response, and posture stay with the security team. Only the enforcement point moves.
Most products in this category make you pick one of those two. A control plane gives the security team a console and leaves engineers out. A developer library gives engineers rules in code and leaves the security team unable to change anything without a deployment. Supporting both against the same enforcement point is what makes the control survive contact with an organization that has both functions.
Roll out runtime controls
The following sequence keeps the rollout measurable:
- Inventory the tools that touch enterprise systems, and rank them by what a failure would cost rather than by traffic.
- Add a guard inside the highest-consequence handlers first.
- Run in dry run against real traffic, so you learn where thresholds actually sit before anything blocks.
- Move rules to enforcing one at a time, starting with budgets, which have the clearest failure signature.
- Tag every decision with the workflow run, so that the activity of a single agent run is reconstructable.
Where this is heading
Enforcing at each action is the floor, and it is the part that you can build first. The harder problem is sequence-shaped.
An agent reads a customer list, then calls an export tool. Each action is permitted, each passes its own check, and together they are an exfiltration. Catching that requires policy that carries what happened earlier in the run into the current decision. Per-action enforcement doesn't deny a later step because of earlier ones. correlationId tags the run so that the sequence is reconstructable, but it doesn't change the current allow or deny. For the same incident worked as a teardown, see anatomy of an agent incident.
What per-action enforcement does do is make the question answerable at all. Without decision points at the consequential actions, and without a correlation ID tying them to a run, there is nothing to reconstruct later. Getting the floor dependable is the prerequisite.
Where Arcjet fits
Securing agents against enterprise systems usually involves several layers. An identity provider issues the agent's credentials. A gateway can govern which MCP servers it can reach. An observability platform records what happened.
Arcjet is the in-code enforcement layer underneath those: a library that runs inside the application, in the path of each tool call, deciding whether to allow a specific action given the user, the arguments, and the budget consumed so far. It reaches the code paths that a gateway can't, because tool handlers and background jobs never route through one.
Arcjet doesn't replace agent identity, and it doesn't do fleet-wide discovery of which agents exist across your company. Those are different layers. For more information about them, see the AI agent security platform category map.
Learn more: AI runtime protection ยท Arcjet Guards
Frequently asked questions
How do I enforce runtime controls on agents accessing enterprise systems?
Put a decision point inside each tool handler that runs before the read or write completes, checking a budget keyed on the identity you are protecting, injection detection on untrusted input including data the agent just read, and sensitive-information detection at the boundary. Identity controls have already passed by that point, because they were designed to.
Why are scoped credentials not enough for AI agents?
Scoped credentials answer whether an agent may reach a system. They cannot answer whether this particular operation, with these arguments, for this user, should happen now. Authorization was granted to the agent rather than to the action.
Is data from our own database untrusted input for an agent?
Yes, once the agent reads it and acts on it. A comment field in a CRM record, a ticket body, or a filename in a document store can all contain text authored outside your organization. The fact that it came from your own systems does not make it safe to feed back into model context.
Can a proxy or WAF enforce controls on agent tool calls?
No. Tool calls and background jobs have no request object, so there is nothing for a proxy or WAF to inspect. The control has to live inside the function that runs the operation.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
A decision inside each tool handler before the read or write. Get budgets, injection checks, and local sensitive-info on the enterprise tool path.